diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3954ca49c11479..69ba432050fa7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: version: 6 - name: Set node version to ${{ matrix.node_version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node_version }} cache: "pnpm" @@ -82,7 +82,7 @@ jobs: version: 6 - name: Set node version to 16 - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: 16 cache: "pnpm" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index eb8f4513707872..9e22af5aed6ee8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: version: 6 - name: Set node version to 16.x - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: 16.x registry-url: https://registry.npmjs.org/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000000..4abbb42e5ef159 --- /dev/null +++ b/.npmrc @@ -0,0 +1,7 @@ +hoist-pattern[]=*eslint* +hoist-pattern[]=*babel* +hoist-pattern[]=*jest* +hoist-pattern[]=@emotion/* +hoist-pattern[]=postcss +hoist-pattern[]=pug +hoist-pattern[]=source-map-support diff --git a/.prettierignore b/.prettierignore index 1692b9d26cfa20..c624a3a21eecfe 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,3 +10,4 @@ pnpm-lock.yaml pnpm-workspace.yaml packages/playground/tsconfig-json-load-error/has-error/tsconfig.json packages/playground/html/invalid.html +packages/playground/worker/classic-worker.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a0e5153f4b8ab..31c1f34f3a4815 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,22 @@ If you want to use break point and explore code execution you can use the ["Run 5. The execution will stop and you'll use the [Debug toolbar](https://code.visualstudio.com/docs/editor/debugging#_debug-actions) to continue, step over, restart the process... +### Debugging errors in Jest tests using Playwright (Chromium) + +Some errors are masked and hidden away because of the layers of abstraction and sandboxed nature added by Jest, Playwright, and Chromium. In order to see what's actually going wrong and the contents of the devtools console in those instances, follow this setup: + +1. Add a `debugger` statement to the `scripts/jestPerTestSetup.ts` -> `afterAll` hook. This will pause execution before the tests quit and the Playwright browser instance exits. + +1. Run the tests with the `debug-serve` script command which will enable remote debugging: `pnpm run debug-serve -- --runInBand resolve`. + +1. Wait for inspector devtools to open in your browser and the debugger to attach. + +1. In the sources panel in the right column, click the play button to resume execution and allow the tests to run which will open a Chromium instance. + +1. Focusing the Chomium instance, you can open the browser devtools and inspect the console there to find the underlying problems. + +1. To close everything, just stop the test process back in your terminal. + ## Testing Vite against external packages You may wish to test your locally-modified copy of Vite against another package that is built with Vite. For pnpm, after building Vite, you can use [`pnpm.overrides`](https://pnpm.io/package_json#pnpmoverrides). Please note that `pnpm.overrides` must be specified in the root `package.json` and you must first list the package as a dependency in the root `package.json`: diff --git a/docs/config/index.md b/docs/config/index.md index d4ee7a96a6fa67..ae70f469f1d445 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -96,6 +96,22 @@ export default defineConfig(async ({ command, mode }) => { }) ``` +### Environment Variables + +Vite doesn't load `.env` files by default as the files to load can only be determined after evaluating the Vite config, for example, the `root` and `envDir` options affects the loading behaviour. However, you can use the exported `loadEnv` helper to load the specific `.env` file if needed. + +```js +import { defineConfig, loadEnv } from 'vite' + +export default defineConfig(({ command, mode }) => { + // Load env file based on `mode` in the current working directory + const env = loadEnv(mode, process.cwd()) + return { + // build specific config + } +}) +``` + ## Shared Options ### root @@ -139,9 +155,11 @@ export default defineConfig(async ({ command, mode }) => { - Replacements are performed only when the match is surrounded by word boundaries (`\b`). + ::: warning Because it's implemented as straightforward text replacements without any syntax analysis, we recommend using `define` for CONSTANTS only. For example, `process.env.FOO` and `__APP_VERSION__` are good fits. But `process` or `global` should not be put into this option. Variables can be shimmed or polyfilled instead. + ::: ::: tip NOTE For TypeScript users, make sure to add the type declarations in the `env.d.ts` or `vite-env.d.ts` file to get type checks and Intellisense. @@ -508,12 +526,14 @@ export default defineConfig(async ({ command, mode }) => { ### server.hmr -- **Type:** `boolean | { protocol?: string, host?: string, port?: number, path?: string, timeout?: number, overlay?: boolean, clientPort?: number, server?: Server }` +- **Type:** `boolean | { protocol?: string, host?: string, port?: number | false, 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. When using `server.middlewareMode` or `server.https`, assigning `server.hmr.server` to your HTTP(S) server will process HMR connection requests through your server. This can be helpful when using self-signed certificates or when you want to expose Vite over a network on a single port. diff --git a/docs/guide/build.md b/docs/guide/build.md index aac86a237b6819..a83e7a8d8b4452 100644 --- a/docs/guide/build.md +++ b/docs/guide/build.md @@ -43,6 +43,20 @@ module.exports = defineConfig({ For example, you can specify multiple Rollup outputs with plugins that are only applied during build. +## Chunking Strategy + +You can configure how chunks are split using `build.rollupOptions.manualChunks` (see [Rollup docs](https://rollupjs.org/guide/en/#outputmanualchunks)). Until Vite 2.7, the default chunking strategy divided the chunks into `index` and `vendor`. It is a good strategy for some SPAs, but it is hard to provide a general solution for every Vite target use case. From Vite 2.8, `manualChunks` is no longer modified by default. You can continue to use the Split Vendor Chunk strategy by adding the `splitVendorChunkPlugin` in your config file: + +```js +// vite.config.js +import { splitVendorChunkPlugin } from 'vite' +module.exports = defineConfig({ + plugins: [splitVendorChunkPlugin()] +}) +``` + +This strategy is also provided as a `splitVendorChunk({ cache: SplitVendorChunkCache })` factory, in case composition with custom logic is needed. `cache.reset()` needs to be called at `buildStart` for build watch mode to work correctly in this case. + ## Rebuild on files changes You can enable rollup watcher with `vite build --watch`. Or, you can directly adjust the underlying [`WatcherOptions`](https://rollupjs.org/guide/en/#watch-options) via `build.watch`: @@ -58,6 +72,8 @@ module.exports = defineConfig({ }) ``` +With the `--watch` flag enabled, changes to the `vite.config.js`, as well as any files to be bundled, will trigger a rebuild. + ## Multi-Page App Suppose you have the following source code structure: diff --git a/packages/playground/assets/__tests__/assets.spec.ts b/packages/playground/assets/__tests__/assets.spec.ts index dfdd8099b062a2..191da897858dd5 100644 --- a/packages/playground/assets/__tests__/assets.spec.ts +++ b/packages/playground/assets/__tests__/assets.spec.ts @@ -8,8 +8,7 @@ import { readManifest, readFile, editFile, - notifyRebuildComplete, - untilUpdated + notifyRebuildComplete } from '../../testUtils' const assetMatch = isBuild @@ -38,7 +37,7 @@ describe('injected scripts', () => { test('html-proxy', async () => { const hasHtmlProxy = await page.$( - 'script[type="module"][src^="/foo/index.html?html-proxy"]' + 'script[type="module"][src="/foo/index.html?html-proxy&index=0.js"]' ) if (isBuild) { expect(hasHtmlProxy).toBeFalsy() @@ -121,6 +120,10 @@ describe('css url() references', () => { const match = isBuild ? `data:image/png;base64` : `/foo/nested/icon.png` expect(await getBg('.css-url-base64-inline')).toMatch(match) expect(await getBg('.css-url-quotes-base64-inline')).toMatch(match) + const icoMatch = isBuild ? `data:image/x-icon;base64` : `favicon.ico` + const el = await page.$(`link.ico`) + const herf = await el.getAttribute('href') + expect(herf).toMatch(icoMatch) }) test('multiple urls on the same line', async () => { @@ -255,15 +258,3 @@ describe('css and assets in css in build watch', () => { }) } }) - -if (!isBuild) { - test('@import in html style tag hmr', async () => { - await untilUpdated(() => getColor('.import-css'), 'rgb(0, 136, 255)') - editFile( - './css/import.css', - (code) => code.replace('#0088ff', '#00ff88'), - true - ) - await untilUpdated(() => getColor('.import-css'), 'rgb(0, 255, 136)') - }) -} diff --git a/packages/playground/assets/favicon.ico b/packages/playground/assets/favicon.ico new file mode 100644 index 00000000000000..d75d248ef0b150 Binary files /dev/null and b/packages/playground/assets/favicon.ico differ diff --git a/packages/playground/assets/index.html b/packages/playground/assets/index.html index 8c5b201f6881a4..e33e9a7cdaaf7d 100644 --- a/packages/playground/assets/index.html +++ b/packages/playground/assets/index.html @@ -2,6 +2,7 @@ + diff --git a/packages/playground/backend-integration/package.json b/packages/playground/backend-integration/package.json index afbdb63d356a8b..b3d9b064db16e2 100644 --- a/packages/playground/backend-integration/package.json +++ b/packages/playground/backend-integration/package.json @@ -10,5 +10,8 @@ }, "dependencies": { "tailwindcss": "^2.2.19" + }, + "devDependencies": { + "fast-glob": "^3.2.11" } } diff --git a/packages/playground/css/__tests__/postcss-plugins-different-dir.spec.ts b/packages/playground/css/__tests__/postcss-plugins-different-dir.spec.ts new file mode 100644 index 00000000000000..48500740b64613 --- /dev/null +++ b/packages/playground/css/__tests__/postcss-plugins-different-dir.spec.ts @@ -0,0 +1,29 @@ +import { getColor, getBgColor } 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 = 5005 + const server = await createServer({ + root: path.join(__dirname, '..', '..', 'tailwind'), + logLevel: 'silent', + server: { + port, + strictPort: true + }, + build: { + // skip transpilation during tests to make it faster + target: 'esnext' + } + }) + await server.listen() + try { + await page.goto(`http://localhost:${port}`) + const tailwindStyle = await page.$('.tailwind-style') + expect(await getBgColor(tailwindStyle)).toBe('rgb(254, 226, 226)') + expect(await getColor(tailwindStyle)).toBe('rgb(136, 136, 136)') + } finally { + await server.close() + } +}) diff --git a/packages/playground/css/package.json b/packages/playground/css/package.json index 13a58874578c09..b45063100be089 100644 --- a/packages/playground/css/package.json +++ b/packages/playground/css/package.json @@ -10,6 +10,7 @@ }, "devDependencies": { "css-dep": "link:./css-dep", + "fast-glob": "^3.2.11", "less": "^4.1.2", "postcss-nested": "^5.0.6", "sass": "^1.43.4", diff --git a/packages/playground/define/__tests__/define.spec.ts b/packages/playground/define/__tests__/define.spec.ts index f5eb78ea4e2766..93ba1ca198fbcc 100644 --- a/packages/playground/define/__tests__/define.spec.ts +++ b/packages/playground/define/__tests__/define.spec.ts @@ -20,4 +20,6 @@ test('string', async () => { expect(await page.textContent('.spread-array')).toBe( JSON.stringify([...defines.__STRING__]) ) + // html would't need to define replacement + expect(await page.textContent('.exp-define')).toBe('__EXP__') }) diff --git a/packages/playground/define/index.html b/packages/playground/define/index.html index bf6a9c59689396..4788ec9f2d2a57 100644 --- a/packages/playground/define/index.html +++ b/packages/playground/define/index.html @@ -9,6 +9,7 @@

Define

process as property:

spread object:

spread array:

+

define variable in html: __EXP__

diff --git a/packages/playground/env-nested/package.json b/packages/playground/env-nested/package.json new file mode 100644 index 00000000000000..8fecc69a41c2f4 --- /dev/null +++ b/packages/playground/env-nested/package.json @@ -0,0 +1,11 @@ +{ + "name": "test-env-nested", + "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/env-nested/vite.config.js b/packages/playground/env-nested/vite.config.js new file mode 100644 index 00000000000000..0e46100698650d --- /dev/null +++ b/packages/playground/env-nested/vite.config.js @@ -0,0 +1,5 @@ +const { defineConfig } = require('vite') + +module.exports = defineConfig({ + envDir: './envs' +}) diff --git a/packages/playground/fs-serve/root/src/index.html b/packages/playground/fs-serve/root/src/index.html index c8b294e86ab0ea..9e4f728a593a91 100644 --- a/packages/playground/fs-serve/root/src/index.html +++ b/packages/playground/fs-serve/root/src/index.html @@ -8,6 +8,10 @@

Safe Fetch


 

 
+

Safe Fetch Subdirectory

+

+

+
 

Unsafe Fetch


 

@@ -42,6 +46,15 @@ 

Denied

.then((data) => { text('.safe-fetch', JSON.stringify(data)) }) + // inside allowed dir, safe fetch + fetch('/src/subdir/safe.txt') + .then((r) => { + text('.safe-fetch-subdir-status', r.status) + return r.text() + }) + .then((data) => { + text('.safe-fetch-subdir', JSON.stringify(data)) + }) // outside of allowed dir, treated as unsafe fetch('/unsafe.txt') diff --git a/packages/playground/fs-serve/root/src/subdir/safe.txt b/packages/playground/fs-serve/root/src/subdir/safe.txt new file mode 100644 index 00000000000000..3f3d0607101642 --- /dev/null +++ b/packages/playground/fs-serve/root/src/subdir/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 3a2425736548ea..fff8d9fe202ebc 100644 --- a/packages/playground/glob-import/__tests__/glob-import.spec.ts +++ b/packages/playground/glob-import/__tests__/glob-import.spec.ts @@ -7,6 +7,9 @@ import { } from '../../testUtils' const filteredResult = { + './alias.js': { + default: 'hi' + }, './foo.js': { msg: 'foo' } @@ -30,11 +33,19 @@ const json = isBuild const allResult = { // JSON file should be properly transformed + '/dir/alias.js': { + default: 'hi' + }, '/dir/baz.json': json, '/dir/foo.js': { msg: 'foo' }, '/dir/index.js': { + globWithAlias: { + './alias.js': { + default: 'hi' + } + }, modules: filteredResult }, '/dir/nested/bar.js': { @@ -80,6 +91,7 @@ if (!isBuild) { '/dir/a.js': {}, ...allResult, '/dir/index.js': { + ...allResult['/dir/index.js'], modules: { './a.js': {}, ...allResult['/dir/index.js'].modules @@ -102,6 +114,7 @@ if (!isBuild) { }, ...allResult, '/dir/index.js': { + ...allResult['/dir/index.js'], modules: { './a.js': { msg: 'a' diff --git a/packages/playground/glob-import/dir/alias.js b/packages/playground/glob-import/dir/alias.js new file mode 100644 index 00000000000000..9c533d93b9a98a --- /dev/null +++ b/packages/playground/glob-import/dir/alias.js @@ -0,0 +1 @@ +export default 'hi' diff --git a/packages/playground/glob-import/dir/index.js b/packages/playground/glob-import/dir/index.js index d13d470e2b2b80..fb87f69f0f3a61 100644 --- a/packages/playground/glob-import/dir/index.js +++ b/packages/playground/glob-import/dir/index.js @@ -1,3 +1,4 @@ const modules = import.meta.globEager('./*.(js|ts)') +const globWithAlias = import.meta.globEager('@dir/al*.js') -export { modules } +export { modules, globWithAlias } diff --git a/packages/playground/glob-import/vite.config.ts b/packages/playground/glob-import/vite.config.ts new file mode 100644 index 00000000000000..abc75b51656503 --- /dev/null +++ b/packages/playground/glob-import/vite.config.ts @@ -0,0 +1,10 @@ +import path from 'path' +import { defineConfig } from 'vite' + +export default defineConfig({ + resolve: { + alias: { + '@dir': path.resolve(__dirname, './dir/') + } + } +}) diff --git a/packages/playground/json/index.html b/packages/playground/json/index.html index 4f6e2b6ae57466..cf16636f91cb68 100644 --- a/packages/playground/json/index.html +++ b/packages/playground/json/index.html @@ -19,6 +19,9 @@

Importing as URL

Raw Import


 
+

JSON Module

+

+
 
+
 
 
-  function text(el, text) {
-    document.querySelector(el).textContent = text
-  }
+
+
diff --git a/packages/playground/optimize-deps/package.json b/packages/playground/optimize-deps/package.json
index 860474673685d3..09c578d684ffe7 100644
--- a/packages/playground/optimize-deps/package.json
+++ b/packages/playground/optimize-deps/package.json
@@ -17,6 +17,7 @@
     "dep-esbuild-plugin-transform": "file:./dep-esbuild-plugin-transform",
     "dep-linked": "link:./dep-linked",
     "dep-linked-include": "link:./dep-linked-include",
+    "dep-not-js": "file:./dep-not-js",
     "lodash-es": "^4.17.21",
     "nested-exclude": "file:./nested-exclude",
     "phoenix": "^1.6.2",
diff --git a/packages/playground/optimize-deps/vite.config.js b/packages/playground/optimize-deps/vite.config.js
index 1a0ac5afab87c9..a989cf1961de11 100644
--- a/packages/playground/optimize-deps/vite.config.js
+++ b/packages/playground/optimize-deps/vite.config.js
@@ -1,3 +1,4 @@
+const fs = require('fs')
 const vue = require('@vitejs/plugin-vue')
 
 /**
@@ -39,6 +40,7 @@ module.exports = {
 
   plugins: [
     vue(),
+    notjs(),
     // for axios request test
     {
       name: 'mock',
@@ -51,3 +53,39 @@ module.exports = {
     }
   ]
 }
+
+// Handles .notjs file, basically remove wrapping  and  tags
+function notjs() {
+  return {
+    name: 'notjs',
+    config() {
+      return {
+        optimizeDeps: {
+          extensions: ['.notjs'],
+          esbuildOptions: {
+            plugins: [
+              {
+                name: 'esbuild-notjs',
+                setup(build) {
+                  build.onLoad({ filter: /\.notjs$/ }, ({ path }) => {
+                    let contents = fs.readFileSync(path, 'utf-8')
+                    contents = contents
+                      .replace('', '')
+                      .replace('', '')
+                    return { contents, loader: 'js' }
+                  })
+                }
+              }
+            ]
+          }
+        }
+      }
+    },
+    transform(code, id) {
+      if (id.endsWith('.notjs')) {
+        code = code.replace('', '').replace('', '')
+        return { code }
+      }
+    }
+  }
+}
diff --git a/packages/playground/resolve/index.html b/packages/playground/resolve/index.html
index 71699035abdd53..c0569345d86837 100644
--- a/packages/playground/resolve/index.html
+++ b/packages/playground/resolve/index.html
@@ -41,6 +41,17 @@ 

fail

+

+ A ts module can import another tsx module using its corresponding jsx file + name +

+

fail

+ +

+ A ts module can import another tsx module using its corresponding js file name +

+

fail

+

Resolve file name containing dot

fail

@@ -137,6 +148,12 @@

resolve package that contains # in path

import { msg as tsExtensionMsg } from './ts-extension' text('.ts-extension', tsExtensionMsg) + import { msgJsx as tsJsxExtensionMsg } from './ts-extension' + text('.jsx-extension', tsJsxExtensionMsg) + + import { msgTsx as tsTsxExtensionMsg } from './ts-extension' + text('.tsx-extension', tsTsxExtensionMsg) + // filename with dot import { bar } from './util/bar.util' text('.dot', bar()) diff --git a/packages/playground/resolve/ts-extension/hellojsx.tsx b/packages/playground/resolve/ts-extension/hellojsx.tsx new file mode 100644 index 00000000000000..a8f610b399b17a --- /dev/null +++ b/packages/playground/resolve/ts-extension/hellojsx.tsx @@ -0,0 +1 @@ +export const msgJsx = '[success] use .jsx extension to import a tsx module' diff --git a/packages/playground/resolve/ts-extension/hellotsx.tsx b/packages/playground/resolve/ts-extension/hellotsx.tsx new file mode 100644 index 00000000000000..b7461ca71ded6c --- /dev/null +++ b/packages/playground/resolve/ts-extension/hellotsx.tsx @@ -0,0 +1 @@ +export const msgTsx = '[success] use .js extension to import a tsx module' diff --git a/packages/playground/resolve/ts-extension/index.ts b/packages/playground/resolve/ts-extension/index.ts index e095619ee4d716..bdb326f8778e64 100644 --- a/packages/playground/resolve/ts-extension/index.ts +++ b/packages/playground/resolve/ts-extension/index.ts @@ -1,3 +1,5 @@ import { msg } from './hello.js' +import { msgJsx } from './hellojsx.jsx' +import { msgTsx } from './hellotsx.js' -export { msg } +export { msg, msgJsx, msgTsx } diff --git a/packages/playground/tailwind/src/views/Page.vue b/packages/playground/tailwind/src/views/Page.vue index ec59b44238c733..764a2a18e54fdb 100644 --- a/packages/playground/tailwind/src/views/Page.vue +++ b/packages/playground/tailwind/src/views/Page.vue @@ -2,7 +2,9 @@

|Page title|

{{ val }}
-
+
Tailwind style
diff --git a/packages/playground/testUtils.ts b/packages/playground/testUtils.ts index 0ae0c6e8766e95..0c8186d4ed121d 100644 --- a/packages/playground/testUtils.ts +++ b/packages/playground/testUtils.ts @@ -64,6 +64,11 @@ export async function getBg(el: string | ElementHandle): Promise { return el.evaluate((el) => getComputedStyle(el as Element).backgroundImage) } +export async function getBgColor(el: string | ElementHandle): Promise { + el = await toEl(el) + return el.evaluate((el) => getComputedStyle(el as Element).backgroundColor) +} + export function readFile(filename: string): string { return fs.readFileSync(path.resolve(testDir, filename), 'utf-8') } diff --git a/packages/playground/vue-jsx/Query.jsx b/packages/playground/vue-jsx/Query.jsx new file mode 100644 index 00000000000000..60de93eafb7b1c --- /dev/null +++ b/packages/playground/vue-jsx/Query.jsx @@ -0,0 +1,12 @@ +import { defineComponent, ref } from 'vue' + +export default defineComponent(() => { + const count = ref(6) + const inc = () => count.value++ + + return () => ( + + ) +}) diff --git a/packages/playground/vue-jsx/__tests__/vue-jsx.spec.ts b/packages/playground/vue-jsx/__tests__/vue-jsx.spec.ts index 64327e64a5f263..999fdc19af51ec 100644 --- a/packages/playground/vue-jsx/__tests__/vue-jsx.spec.ts +++ b/packages/playground/vue-jsx/__tests__/vue-jsx.spec.ts @@ -7,6 +7,7 @@ test('should render', async () => { expect(await page.textContent('.default-tsx')).toMatch('3') expect(await page.textContent('.script')).toMatch('4') expect(await page.textContent('.src-import')).toMatch('5') + expect(await page.textContent('.jsx-with-query')).toMatch('6') expect(await page.textContent('.other-ext')).toMatch('Other Ext') }) @@ -23,6 +24,8 @@ test('should update', async () => { expect(await page.textContent('.script')).toMatch('5') await page.click('.src-import') expect(await page.textContent('.src-import')).toMatch('6') + await page.click('.jsx-with-query') + expect(await page.textContent('.jsx-with-query')).toMatch('7') }) if (!isBuild) { diff --git a/packages/playground/vue-jsx/main.jsx b/packages/playground/vue-jsx/main.jsx index 1a792ea55d2a3c..e304e7788e49e7 100644 --- a/packages/playground/vue-jsx/main.jsx +++ b/packages/playground/vue-jsx/main.jsx @@ -5,6 +5,9 @@ import OtherExt from './OtherExt.tesx' import JsxScript from './Script.vue' import JsxSrcImport from './SrcImport.vue' import JsxSetupSyntax from './setup-syntax-jsx.vue' +// eslint-disable-next-line +import JsxWithQuery from './Query.jsx?query=true' + function App() { return ( <> @@ -16,6 +19,7 @@ function App() { + ) } diff --git a/packages/playground/vue-jsx/vite.config.js b/packages/playground/vue-jsx/vite.config.js index 3ec89a003d79f4..d6eb84e05f4e4a 100644 --- a/packages/playground/vue-jsx/vite.config.js +++ b/packages/playground/vue-jsx/vite.config.js @@ -9,7 +9,28 @@ module.exports = { vueJsxPlugin({ include: [/\.tesx$/, /\.[jt]sx$/] }), - vuePlugin() + vuePlugin(), + { + name: 'jsx-query-plugin', + transform(code, id) { + if (id.includes('?query=true')) { + return ` +import { createVNode as _createVNode } from "vue"; +import { defineComponent, ref } from 'vue'; +export default defineComponent(() => { + const count = ref(6); + + const inc = () => count.value++; + + return () => _createVNode("button", { + "class": "jsx-with-query", + "onClick": inc + }, [count.value]); +}); +` + } + } + } ], build: { // to make tests faster diff --git a/packages/playground/vue/public/favicon.ico b/packages/playground/vue/public/favicon.ico new file mode 100644 index 00000000000000..df36fcfb72584e Binary files /dev/null and b/packages/playground/vue/public/favicon.ico differ diff --git a/packages/playground/vue/vite.config.ts b/packages/playground/vue/vite.config.ts index 82efdac5e9f876..f99a68ce8b6b10 100644 --- a/packages/playground/vue/vite.config.ts +++ b/packages/playground/vue/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'vite' +import { defineConfig, splitVendorChunkPlugin } from 'vite' import vuePlugin from '@vitejs/plugin-vue' import { vueI18nPlugin } from './CustomBlockPlugin' @@ -12,11 +12,22 @@ export default defineConfig({ vuePlugin({ reactivityTransform: true }), + splitVendorChunkPlugin(), vueI18nPlugin ], build: { // to make tests faster - minify: false + minify: false, + rollupOptions: { + output: { + // Test splitVendorChunkPlugin composition + manualChunks(id) { + if (id.includes('src-import')) { + return 'src-import' + } + } + } + } }, css: { modules: { diff --git a/packages/playground/worker/__tests__/worker.spec.ts b/packages/playground/worker/__tests__/worker.spec.ts index 5992a37f933f2a..1c33e6ec68daf9 100644 --- a/packages/playground/worker/__tests__/worker.spec.ts +++ b/packages/playground/worker/__tests__/worker.spec.ts @@ -56,7 +56,7 @@ if (isBuild) { // assert correct files test('inlined code generation', async () => { const files = fs.readdirSync(assetsDir) - expect(files.length).toBe(6) + expect(files.length).toBe(8) const index = files.find((f) => f.includes('index')) const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') const worker = files.find((f) => f.includes('my-worker')) @@ -88,3 +88,8 @@ if (isBuild) { }) }) } + +test('classic worker is run', 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/classic-worker.js b/packages/playground/worker/classic-worker.js new file mode 100644 index 00000000000000..bb6f9c3f49fc84 --- /dev/null +++ b/packages/playground/worker/classic-worker.js @@ -0,0 +1,29 @@ +// prettier-ignore +function text(el, text) { + document.querySelector(el).textContent = text +} + +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 + ) +}) +classicSharedWorker.port.start() diff --git a/packages/playground/worker/index.html b/packages/playground/worker/index.html index be0b3becb5f94f..10207453712aa2 100644 --- a/packages/playground/worker/index.html +++ b/packages/playground/worker/index.html @@ -20,18 +20,25 @@ 0
-

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

+

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

-

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

+

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

+

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

+
+ +

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

+
+ ` - ) - } - await traverseHtml(html, htmlPath, (node) => { if (node.type !== NodeTypes.ELEMENT) { return @@ -135,16 +105,39 @@ const devHtmlHook: IndexHtmlTransformHook = async ( // script tags if (node.tag === 'script') { const { src, isModule } = getScriptInfo(node) + if (isModule) { + scriptModuleIndex++ + } if (src) { processNodeUrl(src, s, config, htmlPath, originalUrl, moduleGraph) } else if (isModule) { - addInlineModule(node, 'js') - } - } + const url = filePath.replace(normalizePath(config.root), '') + + const contents = node.children + .map((child: any) => child.content || '') + .join('') - if (node.tag === 'style' && node.children.length) { - addInlineModule(node, 'css') + // add HTML Proxy to Map + addToHTMLProxyCache(config, url, scriptModuleIndex, contents) + + // inline js module. convert to src="proxy" + const modulePath = `${ + config.base + htmlPath.slice(1) + }?html-proxy&index=${scriptModuleIndex}.js` + + // invalidate the module so the newly cached contents will be served + const module = server?.moduleGraph.getModuleById(modulePath) + if (module) { + server?.moduleGraph.invalidateModule(module) + } + + s.overwrite( + node.loc.start.offset, + node.loc.end.offset, + `` + ) + } } // elements with [href/src] attrs diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index 0b8e2db93255ed..a6623338783cc8 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -4,17 +4,17 @@ import type { Options } from 'sirv' import sirv from 'sirv' import type { Connect } from 'types/connect' import type { ViteDevServer } from '../..' -import { normalizePath } from '../..' import { FS_PREFIX } from '../../constants' import { cleanUrl, - ensureLeadingSlash, fsPathFromId, + fsPathFromUrl, isImportRequest, isInternalRequest, isWindows, slash, - isFileReadable + isFileReadable, + isParentDirectory } from '../../utils' import { isMatch } from 'micromatch' @@ -148,15 +148,14 @@ export function isFileServingAllowed( ): boolean { if (!server.config.server.fs.strict) return true - const cleanedUrl = cleanUrl(url) - const file = ensureLeadingSlash(normalizePath(cleanedUrl)) + const file = fsPathFromUrl(url) if (server.config.server.fs.deny.some((i) => isMatch(file, i, _matchOptions))) return false if (server.moduleGraph.safeModulesPath.has(file)) return true - if (server.config.server.fs.allow.some((i) => file.startsWith(i + '/'))) + if (server.config.server.fs.allow.some((dir) => isParentDirectory(dir, file))) return true return false diff --git a/packages/vite/src/node/server/middlewares/transform.ts b/packages/vite/src/node/server/middlewares/transform.ts index 15f2355e0e389c..ae7bec6e185113 100644 --- a/packages/vite/src/node/server/middlewares/transform.ts +++ b/packages/vite/src/node/server/middlewares/transform.ts @@ -1,3 +1,4 @@ +import { promises as fs } from 'fs' import path from 'path' import type { ViteDevServer } from '..' import type { Connect } from 'types/connect' @@ -11,28 +12,29 @@ import { prettifyUrl, removeImportQuery, removeTimestampQuery, - unwrapId + unwrapId, + fsPathFromId, + ensureVolumeInPath } from '../../utils' import { send } from '../send' import { transformRequest } from '../transformRequest' import { isHTMLProxy } from '../../plugins/html' import colors from 'picocolors' import { - CLIENT_PUBLIC_PATH, DEP_VERSION_RE, - NULL_BYTE_PLACEHOLDER + NULL_BYTE_PLACEHOLDER, + FS_PREFIX } from '../../constants' import { isCSSRequest, isDirectCSSRequest, isDirectRequest } from '../../plugins/css' - -/** - * Time (ms) Vite has to full-reload the page before returning - * an empty response. - */ -const NEW_DEPENDENCY_BUILD_TIMEOUT = 1000 +import { + ERR_OPTIMIZE_DEPS_PROCESSING_ERROR, + ERR_OUTDATED_OPTIMIZED_DEP +} from '../../plugins/optimizedDeps' +import { createIsOptimizedDepUrl } from '../../optimizer' const debugCache = createDebugger('vite:cache') const isDebug = !!process.env.DEBUG @@ -43,19 +45,11 @@ export function transformMiddleware( server: ViteDevServer ): Connect.NextHandleFunction { const { - config: { root, logger, cacheDir }, + config: { root, logger }, moduleGraph } = server - // determine the url prefix of files inside cache directory - const cacheDirRelative = normalizePath(path.relative(root, cacheDir)) - const cacheDirPrefix = cacheDirRelative.startsWith('../') - ? // if the cache directory is outside root, the url prefix would be something - // like '/@fs/absolute/path/to/node_modules/.vite' - `/@fs/${normalizePath(cacheDir).replace(/^\//, '')}` - : // if the cache directory is inside root, the url prefix would be something - // like '/node_modules/.vite' - `/${cacheDirRelative}` + const isOptimizedDepUrl = createIsOptimizedDepUrl(server.config) // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...` return async function viteTransformMiddleware(req, res, next) { @@ -63,36 +57,6 @@ export function transformMiddleware( return next() } - if ( - server._pendingReload && - // always allow vite client requests so that it can trigger page reload - !req.url?.startsWith(CLIENT_PUBLIC_PATH) && - !req.url?.includes('vite/dist/client') - ) { - try { - // missing dep pending reload, hold request until reload happens - await Promise.race([ - server._pendingReload, - // If the refresh has not happened after timeout, Vite considers - // something unexpected has happened. In this case, Vite - // returns an empty response that will error. - new Promise((_, reject) => - setTimeout(reject, NEW_DEPENDENCY_BUILD_TIMEOUT) - ) - ]) - } catch { - // Don't do anything if response has already been sent - if (!res.writableEnded) { - // status code request timeout - res.statusCode = 408 - res.end( - `

[vite] Something unexpected happened while optimizing "${req.url}"

` + - `

The current page should have reloaded by now

` - ) - } - return - } - } let url: string try { url = decodeURI(removeTimestampQuery(req.url!)).replace( @@ -109,15 +73,47 @@ export function transformMiddleware( const isSourceMap = withoutQuery.endsWith('.map') // since we generate source map references, handle those requests here if (isSourceMap) { - const originalUrl = url.replace(/\.map($|\?)/, '$1') - const map = (await moduleGraph.getModuleByUrl(originalUrl, false)) - ?.transformResult?.map - if (map) { - return send(req, res, JSON.stringify(map), 'json', { - headers: server.config.server.headers - }) + if (isOptimizedDepUrl(url)) { + // If the browser is requesting a source map for an optimized dep, it + // means that the dependency has already been pre-bundled and loaded + const mapFile = url.startsWith(FS_PREFIX) + ? fsPathFromId(url) + : normalizePath( + ensureVolumeInPath(path.resolve(root, url.slice(1))) + ) + try { + const map = await fs.readFile(mapFile, 'utf-8') + return send(req, res, map, 'json', { + headers: server.config.server.headers + }) + } catch (e) { + // Outdated source map request for optimized deps, this isn't an error + // but part of the normal flow when re-optimizing after missing deps + // Send back an empty source map so the browser doesn't issue warnings + const dummySourceMap = { + version: 3, + file: mapFile.replace(/\.map$/, ''), + sources: [], + sourcesContent: [], + names: [], + mappings: ';;;;;;;;;' + } + return send(req, res, JSON.stringify(dummySourceMap), 'json', { + cacheControl: 'no-cache', + headers: server.config.server.headers + }) + } } else { - return next() + const originalUrl = url.replace(/\.map($|\?)/, '$1') + const map = (await moduleGraph.getModuleByUrl(originalUrl, false)) + ?.transformResult?.map + if (map) { + return send(req, res, JSON.stringify(map), 'json', { + headers: server.config.server.headers + }) + } else { + return next() + } } } @@ -179,9 +175,7 @@ export function transformMiddleware( }) if (result) { const type = isDirectCSSRequest(url) ? 'css' : 'js' - const isDep = - DEP_VERSION_RE.test(url) || - (cacheDirPrefix && url.startsWith(cacheDirPrefix)) + const isDep = DEP_VERSION_RE.test(url) || isOptimizedDepUrl(url) return send(req, res, result.code, type, { etag: result.etag, // allow browser to cache npm deps! @@ -192,6 +186,30 @@ export function transformMiddleware( } } } catch (e) { + if (e?.code === ERR_OPTIMIZE_DEPS_PROCESSING_ERROR) { + if (!res.writableEnded) { + // Don't do anything if response has already been sent + res.statusCode = 504 // status code request timeout + res.end() + } + // This timeout is unexpected + logger.error(e.message) + return + } + if (e?.code === ERR_OUTDATED_OPTIMIZED_DEP) { + if (!res.writableEnded) { + // Don't do anything if response has already been sent + res.statusCode = 504 // status code request timeout + res.end() + } + // We don't need to log an error in this case, the request + // is outdated because new dependencies were discovered and + // the new pre-bundle dependendencies have changed. + // A full-page reload has been issued, and these old requests + // can't be properly fullfilled. This isn't an unexpected + // error but a normal part of the missing deps discovery flow + return + } return next(e) } diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index 498148933740ef..c8da5e4ab6e82f 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -42,6 +42,7 @@ export function createWebSocketServer( } else { const websocketServerOptions: ServerOptions = {} const port = (hmr && hmr.port) || 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 // create an inline https server and mount the websocket server to it @@ -60,11 +61,14 @@ export function createWebSocketServer( res.end(body) }) - httpsServer.listen(port) + httpsServer.listen(port, host) websocketServerOptions.server = httpsServer } else { // we don't need to serve over https, just let ws handle its own server websocketServerOptions.port = port + if (host) { + websocketServerOptions.host = host + } } // vite dev server in middleware mode diff --git a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts index f91444b49636a1..e086365ee25f16 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts @@ -304,8 +304,8 @@ test('should declare variable for imported super class', async () => { const Foo = __vite_ssr_import_0__.Foo; class A extends Foo {} class B extends Foo {} - Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: A }); - Object.defineProperty(__vite_ssr_exports__, \\"B\\", { enumerable: true, configurable: true, get(){ return B }});" + Object.defineProperty(__vite_ssr_exports__, \\"B\\", { enumerable: true, configurable: true, get(){ return B }}); + Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: A });" `) }) @@ -351,8 +351,8 @@ test('should handle default export variants', async () => { ).toMatchInlineSnapshot(` "class A {} class B extends A {} - Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: A }); - Object.defineProperty(__vite_ssr_exports__, \\"B\\", { enumerable: true, configurable: true, get(){ return B }});" + Object.defineProperty(__vite_ssr_exports__, \\"B\\", { enumerable: true, configurable: true, get(){ return B }}); + Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: A });" `) }) @@ -610,7 +610,7 @@ test('jsx', async () => { const code = ` import React from 'react' import { Foo, Slot } from 'foo' - + function Bar({ Slot = }) { return ( <> @@ -633,3 +633,27 @@ test('jsx', async () => { " `) }) + +test('continuous exports', async () => { + expect( + ( + await ssrTransform( + ` +export function fn1() { +}export function fn2() { +} + `, + null, + null + ) + ).code + ).toMatchInlineSnapshot(` + " + function fn1() { + } + Object.defineProperty(__vite_ssr_exports__, \\"fn1\\", { enumerable: true, configurable: true, get(){ return fn1 }});function fn2() { + } + Object.defineProperty(__vite_ssr_exports__, \\"fn2\\", { enumerable: true, configurable: true, get(){ return fn2 }}); + " + `) +}) diff --git a/packages/vite/src/node/ssr/ssrManifestPlugin.ts b/packages/vite/src/node/ssr/ssrManifestPlugin.ts index 351c91349a3ca3..1e00853402ba5d 100644 --- a/packages/vite/src/node/ssr/ssrManifestPlugin.ts +++ b/packages/vite/src/node/ssr/ssrManifestPlugin.ts @@ -4,8 +4,6 @@ import type { ImportSpecifier } from 'es-module-lexer' import type { OutputChunk } from 'rollup' import type { ResolvedConfig } from '..' import type { Plugin } from '../plugin' -import { chunkToEmittedCssFileMap } from '../plugins/css' -import { chunkToEmittedAssetsMap } from '../plugins/asset' import { preloadMethod } from '../plugins/importAnalysisBuild' import { normalizePath } from '../utils' @@ -20,29 +18,21 @@ export function ssrManifestPlugin(config: ResolvedConfig): Plugin { for (const file in bundle) { const chunk = bundle[file] if (chunk.type === 'chunk') { - // links for certain entry chunks are already generated in static HTML - // in those cases we only need to record info for non-entry chunks - const cssFiles = chunk.isEntry - ? null - : chunkToEmittedCssFileMap.get(chunk) - const assetFiles = chunkToEmittedAssetsMap.get(chunk) for (const id in chunk.modules) { const normalizedId = normalizePath(relative(config.root, id)) const mappedChunks = ssrManifest[normalizedId] ?? (ssrManifest[normalizedId] = []) if (!chunk.isEntry) { mappedChunks.push(base + chunk.fileName) - } - if (cssFiles) { - cssFiles.forEach((file) => { - mappedChunks.push(base + file) - }) - } - if (assetFiles) { - assetFiles.forEach((file) => { + // tags for entry chunks are already generated in static HTML, + // so we only need to record info for non-entry chunks. + chunk.viteMetadata.importedCss.forEach((file) => { mappedChunks.push(base + file) }) } + chunk.viteMetadata.importedAssets.forEach((file) => { + mappedChunks.push(base + file) + }) } if (chunk.code.includes(preloadMethod)) { // generate css deps map @@ -74,12 +64,9 @@ export function ssrManifestPlugin(config: ResolvedConfig): Plugin { analyzed.add(filename) const chunk = bundle[filename] as OutputChunk | undefined if (chunk) { - const cssFiles = chunkToEmittedCssFileMap.get(chunk) - if (cssFiles) { - cssFiles.forEach((file) => { - deps.push(`/${file}`) - }) - } + chunk.viteMetadata.importedCss.forEach((file) => { + deps.push(`/${file}`) + }) chunk.imports.forEach(addDeps) } } diff --git a/packages/vite/src/node/ssr/ssrTransform.ts b/packages/vite/src/node/ssr/ssrTransform.ts index b4eee713e707bc..36496b8168ad36 100644 --- a/packages/vite/src/node/ssr/ssrTransform.ts +++ b/packages/vite/src/node/ssr/ssrTransform.ts @@ -66,7 +66,7 @@ export async function ssrTransform( } function defineExport(position: number, name: string, local = name) { - s.appendRight( + s.appendLeft( position, `\nObject.defineProperty(${ssrModuleExportsKey}, "${name}", ` + `{ enumerable: true, configurable: true, get(){ return ${local} }});` diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 818924ad2f785a..e5b903a05a41ce 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -9,7 +9,8 @@ import { DEFAULT_EXTENSIONS, VALID_ID_PREFIX, CLIENT_PUBLIC_PATH, - ENV_PUBLIC_PATH + ENV_PUBLIC_PATH, + CLIENT_ENTRY } from './constants' import resolve from 'resolve' import { builtinModules } from 'module' @@ -139,7 +140,25 @@ export function createDebugger( } } +function testCaseInsensitiveFS() { + if (!CLIENT_ENTRY.endsWith('client.mjs')) { + throw new Error( + `cannot test case insensitive FS, CLIENT_ENTRY const doesn't contain client.mjs` + ) + } + if (!fs.existsSync(CLIENT_ENTRY)) { + throw new Error( + 'cannot test case insensitive FS, CLIENT_ENTRY does not point to an existing file: ' + + CLIENT_ENTRY + ) + } + return fs.existsSync(CLIENT_ENTRY.replace('client.mjs', 'cLiEnT.mjs')) +} + +export const isCaseInsensitiveFS = testCaseInsensitiveFS() + export const isWindows = os.platform() === 'win32' + const VOLUME_RE = /^[A-Z]:/i export function normalizePath(id: string): string { @@ -147,12 +166,37 @@ export function normalizePath(id: string): string { } export function fsPathFromId(id: string): string { - const fsPath = normalizePath(id.slice(FS_PREFIX.length)) + const fsPath = normalizePath( + id.startsWith(FS_PREFIX) ? id.slice(FS_PREFIX.length) : id + ) return fsPath.startsWith('/') || fsPath.match(VOLUME_RE) ? fsPath : `/${fsPath}` } +export function fsPathFromUrl(url: string): string { + return fsPathFromId(cleanUrl(url)) +} + +/** + * Check if dir is a parent of file + * + * Warning: parameters are not validated, only works with normalized absolute paths + * + * @param dir - normalized absolute path + * @param file - normalized absolute path + * @returns true if dir is a parent of file + */ +export function isParentDirectory(dir: string, file: string): boolean { + if (!dir.endsWith('/')) { + dir = `${dir}/` + } + return ( + file.startsWith(dir) || + (isCaseInsensitiveFS && file.toLowerCase().startsWith(dir.toLowerCase())) + ) +} + export function ensureVolumeInPath(file: string): string { return isWindows ? path.resolve(file) : file } @@ -189,8 +233,14 @@ const knownTsOutputRE = /\.(js|mjs|cjs|jsx)$/ export const isTsRequest = (url: string) => knownTsRE.test(cleanUrl(url)) export const isPossibleTsOutput = (url: string) => knownTsOutputRE.test(cleanUrl(url)) -export const getTsSrcPath = (filename: string) => - filename.replace(/\.([cm])?(js)(x?)(\?|$)/, '.$1ts$3') +export function getPotentialTsSrcPaths(filePath: string) { + const [name, type, query = ''] = filePath.split(/(\.(?:[cm]?js|jsx))(\?.*)?$/) + const paths = [name + type.replace('js', 'ts') + query] + if (!type.endsWith('x')) { + paths.push(name + type.replace('js', 'tsx') + query) + } + return paths +} const importQueryRE = /(\?|&)import=?(?:&|$)/ const internalPrefixes = [ @@ -292,20 +342,28 @@ export function isDefined(value: T | undefined | null): value is T { return value != null } +interface LookupFileOptions { + pathOnly?: boolean + rootDir?: string +} + export function lookupFile( dir: string, formats: string[], - pathOnly = false + options?: LookupFileOptions ): string | undefined { for (const format of formats) { const fullPath = path.join(dir, format) if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) { - return pathOnly ? fullPath : fs.readFileSync(fullPath, 'utf-8') + return options?.pathOnly ? fullPath : fs.readFileSync(fullPath, 'utf-8') } } const parentDir = path.dirname(dir) - if (parentDir !== dir) { - return lookupFile(parentDir, formats, pathOnly) + if ( + parentDir !== dir && + (!options?.rootDir || parentDir.startsWith(options?.rootDir)) + ) { + return lookupFile(parentDir, formats, options) } } @@ -466,10 +524,6 @@ export function copyDir(srcDir: string, destDir: string): void { } } -export function ensureLeadingSlash(path: string): string { - return !path.startsWith('/') ? '/' + path : path -} - export function ensureWatchedFile( watcher: FSWatcher, file: string | null, @@ -638,3 +692,5 @@ export function parseRequest(id: string): Record | null { } return Object.fromEntries(new URLSearchParams(search.slice(1))) } + +export const blankReplacer = (match: string) => ' '.repeat(match.length) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab2b4a7833c686..9982e48276c096 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,9 +115,12 @@ importers: packages/playground/backend-integration: specifiers: + fast-glob: ^3.2.11 tailwindcss: ^2.2.19 dependencies: tailwindcss: 2.2.19_ts-node@10.4.0 + devDependencies: + fast-glob: 3.2.11 packages/playground/cli: specifiers: {} @@ -128,12 +131,14 @@ importers: packages/playground/css: specifiers: css-dep: link:./css-dep + fast-glob: ^3.2.11 less: ^4.1.2 postcss-nested: ^5.0.6 sass: ^1.43.4 stylus: ^0.55.0 devDependencies: css-dep: link:css-dep + fast-glob: 3.2.11 less: 4.1.2 postcss-nested: 5.0.6 sass: 1.45.1 @@ -203,21 +208,33 @@ importers: packages/playground/json: specifiers: + json-module: file:./json-module vue: ^3.2.25 devDependencies: + json-module: link:json-module vue: 3.2.26 + packages/playground/json/json-module: + specifiers: {} + packages/playground/legacy: specifiers: '@vitejs/plugin-legacy': workspace:* + express: ^4.17.1 devDependencies: '@vitejs/plugin-legacy': link:../../plugin-legacy + express: 4.17.2 packages/playground/lib: specifiers: {} packages/playground/multiple-entrypoints: - specifiers: {} + specifiers: + fast-glob: ^3.2.11 + sass: ^1.43.4 + devDependencies: + fast-glob: 3.2.11 + sass: 1.45.1 packages/playground/nested-deps: specifiers: @@ -278,6 +295,7 @@ importers: dep-esbuild-plugin-transform: file:./dep-esbuild-plugin-transform dep-linked: link:./dep-linked dep-linked-include: link:./dep-linked-include + dep-not-js: file:./dep-not-js lodash-es: ^4.17.21 nested-exclude: file:./nested-exclude phoenix: ^1.6.2 @@ -295,6 +313,7 @@ importers: dep-esbuild-plugin-transform: link:dep-esbuild-plugin-transform dep-linked: link:dep-linked dep-linked-include: link:dep-linked-include + dep-not-js: link:dep-not-js lodash-es: 4.17.21 nested-exclude: link:nested-exclude phoenix: 1.6.5 @@ -328,6 +347,9 @@ importers: dependencies: react: 17.0.2 + packages/playground/optimize-deps/dep-not-js: + specifiers: {} + packages/playground/optimize-deps/nested-exclude: specifiers: nested-include: link:./nested-include @@ -792,7 +814,7 @@ importers: picocolors: ^1.0.0 postcss: ^8.4.6 postcss-import: ^14.0.2 - postcss-load-config: ^3.1.1 + postcss-load-config: ^3.1.3 postcss-modules: ^4.3.0 resolve: ^1.22.0 resolve.exports: ^1.1.0 @@ -803,7 +825,7 @@ importers: source-map-support: ^0.5.21 strip-ansi: ^6.0.1 terser: ^5.10.0 - tsconfck: 1.1.2 + tsconfck: ^1.2.0 tslib: ^2.3.1 types: link:./types ws: ^8.5.0 @@ -867,7 +889,7 @@ importers: periscopic: 2.0.3 picocolors: 1.0.0 postcss-import: 14.0.2_postcss@8.4.6 - postcss-load-config: 3.1.1_ts-node@10.4.0 + postcss-load-config: 3.1.3_ts-node@10.4.0 postcss-modules: 4.3.0_postcss@8.4.6 resolve.exports: 1.1.0 rollup-plugin-license: 2.6.1_rollup@2.62.0 @@ -876,7 +898,7 @@ importers: source-map-support: 0.5.21 strip-ansi: 6.0.1 terser: 5.10.0_acorn@8.7.0 - tsconfck: 1.1.2_typescript@4.5.4 + tsconfck: 1.2.0_typescript@4.5.4 tslib: 2.3.1 types: link:types ws: 8.5.0 @@ -3103,7 +3125,7 @@ packages: resolution: {integrity: sha512-oagLNqpfNv7CvmyMoexMDNyVDSiq1rya0AEUgcLlNHdHgNl6U/hi8xY370n5y+ZIFEXOx0J4B1qF2NDjMRxklA==} engines: {node: '>=6.0.0'} dependencies: - pvutils: 1.0.17 + pvutils: 1.1.2 dev: true /assert-never/1.2.1: @@ -4811,7 +4833,6 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.4 - dev: true /fast-glob/3.2.7: resolution: {integrity: sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==} @@ -4822,6 +4843,7 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.4 + dev: true /fast-json-stable-stringify/2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -6140,7 +6162,7 @@ packages: '@types/node': 16.11.22 chalk: 4.1.2 ci-info: 3.3.0 - graceful-fs: 4.2.8 + graceful-fs: 4.2.9 picomatch: 2.3.0 dev: true @@ -7350,8 +7372,8 @@ packages: yaml: 1.10.2 dev: false - /postcss-load-config/3.1.1_ts-node@10.4.0: - resolution: {integrity: sha512-c/9XYboIbSEUZpiD1UQD0IKiUe8n9WHYV7YFe7X7J+ZwCsEKkUJSFWjS9hBU1RR9THR7jMXst8sxiqP0jjo2mg==} + /postcss-load-config/3.1.3_ts-node@10.4.0: + resolution: {integrity: sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw==} engines: {node: '>= 10'} peerDependencies: ts-node: '>=9.0.0' @@ -7690,8 +7712,8 @@ packages: tslib: 2.3.1 dev: true - /pvutils/1.0.17: - resolution: {integrity: sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==} + /pvutils/1.1.2: + resolution: {integrity: sha512-wlo0BUInyP+ZgBJHV8PnJW8S2HubdQfMMip8B9yXr9aFlauJFuF1jZ/RWFmzGYitC7GxkxqXdwbY9/R97v+Cqg==} engines: {node: '>=6.0.0'} dev: true @@ -8611,7 +8633,7 @@ packages: detective: 5.2.0 didyoumean: 1.2.2 dlv: 1.1.3 - fast-glob: 3.2.7 + fast-glob: 3.2.11 fs-extra: 10.0.0 glob-parent: 6.0.2 html-tags: 3.1.0 @@ -8655,7 +8677,7 @@ packages: detective: 5.2.0 didyoumean: 1.2.2 dlv: 1.1.3 - fast-glob: 3.2.7 + fast-glob: 3.2.11 fs-extra: 10.0.0 glob-parent: 6.0.2 html-tags: 3.1.0 @@ -8916,8 +8938,8 @@ packages: yn: 3.1.1 dev: true - /tsconfck/1.1.2_typescript@4.5.4: - resolution: {integrity: sha512-n6SkiUQ784Uu2pviyd0j2kspm/fipdjO5jZuR8zUHVVoQeRnR6BSV5zlbheDmpyP0lj7GvHWvFcaxxhUFrzQWQ==} + /tsconfck/1.2.0_typescript@4.5.4: + resolution: {integrity: sha512-uKBM6x6i5e7Tfof5Zhll2ypgYKwWZhsxsHOoCH/7enlcHjjlcZ8FnxFrqgVFtpN6ymzZX3zoDaVGIlkh/g4x6w==} engines: {node: ^12.20 || ^14.13.1 || >= 16} hasBin: true peerDependencies: