From 2f48e145e1c8e603ee2242cd1f2b4f78c672c699 Mon Sep 17 00:00:00 2001 From: patak-js Date: Thu, 4 Nov 2021 16:14:07 +0100 Subject: [PATCH 1/3] docs: virtual modules internal convention --- docs/guide/api-plugin.md | 6 ++--- .../resolve/__tests__/resolve.spec.ts | 4 ++++ packages/playground/resolve/index.html | 6 +++++ packages/playground/resolve/vite.config.js | 22 ++++++++++++++++--- packages/vite/src/node/constants.ts | 12 +++++++--- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index 58afcfea832dfe..e3f5df56a23a34 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -17,7 +17,7 @@ When learning, debugging, or authoring plugins we suggest including [vite-plugin ## Conventions -If the plugin doesn't use Vite specific hooks and can be implemented as a [Compatible Rollup Plugin](#rollup-plugin-compatibility), then it is recommended to use the [Rollup Plugin naming conventions](https://rollupjs.org/guide/en/#conventions) (except for virtual modules naming, see note below). +If the plugin doesn't use Vite specific hooks and can be implemented as a [Compatible Rollup Plugin](#rollup-plugin-compatibility), then it is recommended to use the [Rollup Plugin naming conventions](https://rollupjs.org/guide/en/#conventions). - Rollup Plugins should have a clear name with `rollup-plugin-` prefix. - Include `rollup-plugin` and `vite-plugin` keywords in package.json. @@ -36,9 +36,7 @@ If your plugin is only going to work for a particular framework, its name should - `vite-plugin-react-` prefix for React Plugins - `vite-plugin-svelte-` prefix for Svelte Plugins -Rollup recommends prefixing the module ID for 'virtual modules' (e.g. for helper functions) with `\0`. This prevents other plugins from trying to process it. But this convention for paths isn't browser-friendly. - -Vite convention for virtual modules is to prefix the path with `virtual:`. If possible the plugin name should be used as a namespace to avoid collisions with other plugins in the ecosystem. For example, a `vite-plugin-posts` could ask users to import a `virtual:posts` or `virtual:posts/helpers` virtual modules to get build time information. +Vite convention for virtual modules is to prefix the use facing path with `virtual:`. If possible the plugin name should be used as a namespace to avoid collisions with other plugins in the ecosystem. For example, a `vite-plugin-posts` could ask users to import a `virtual:posts` or `virtual:posts/helpers` virtual modules to get build time information. Internally, plugins that use virtual modules should prefix the module ID with `\0` while resolving the id, a convention from the rollup ecosystem. This prevents other plugins from trying to process the id (like node resolution), and core features like sourcemaps can use this info to differentiate between virtual modules and regular files. `\0` is not a permitted char in import URLs so we have to replace them during import analysis. A `\0{id}` virtual id ends up encoded as `/@id/__x00__{id}` during dev in the browser. The id will be decoded back before entering the plugins pipeline, so this is not seen by plugins hooks code. ## Plugins config diff --git a/packages/playground/resolve/__tests__/resolve.spec.ts b/packages/playground/resolve/__tests__/resolve.spec.ts index b1524e1e42aa08..97e4a5dd0add7b 100644 --- a/packages/playground/resolve/__tests__/resolve.spec.ts +++ b/packages/playground/resolve/__tests__/resolve.spec.ts @@ -78,6 +78,10 @@ test('plugin resolved virtual file', async () => { expect(await page.textContent('.virtual')).toMatch('[success]') }) +test('plugin resolved custom virtual file', async () => { + expect(await page.textContent('.custom-virtual')).toMatch('[success]') +}) + test('resolve inline package', async () => { expect(await page.textContent('.inline-pkg')).toMatch('[success]') }) diff --git a/packages/playground/resolve/index.html b/packages/playground/resolve/index.html index 9dc6525fcd7a43..db0a4bc54f1ad7 100644 --- a/packages/playground/resolve/index.html +++ b/packages/playground/resolve/index.html @@ -53,6 +53,9 @@

Monorepo linked dep

Plugin resolved virtual file

+

Plugin resolved custom virtual file

+

+

Inline package

@@ -180,6 +183,9 @@

resolve package that contains # in path

import { msg as virtualMsg } from '@virtual-file' text('.virtual', virtualMsg) + import { msg as customVirtualMsg } from '@custom-virtual-file' + text('.custom-virtual', customVirtualMsg) + import { msg as inlineMsg } from './inline-package' text('.inline-pkg', inlineMsg) diff --git a/packages/playground/resolve/vite.config.js b/packages/playground/resolve/vite.config.js index e7d531097add7c..036033e6a5f220 100644 --- a/packages/playground/resolve/vite.config.js +++ b/packages/playground/resolve/vite.config.js @@ -1,4 +1,7 @@ const virtualFile = '@virtual-file' +const virtualId = '\0' + virtualFile + +const customVirtualFile = '@custom-virtual-file' module.exports = { resolve: { @@ -8,15 +11,28 @@ module.exports = { }, plugins: [ { - name: 'custom-resolve', + name: 'virtual-module', resolveId(id) { if (id === virtualFile) { + return virtualId + } + }, + load(id) { + if (id === virtualId) { + return `export const msg = "[success] from conventional virtual file"` + } + } + }, + { + name: 'custom-resolve', + resolveId(id) { + if (id === customVirtualFile) { return id } }, load(id) { - if (id === virtualFile) { - return `export const msg = "[success] from virtual file"` + if (id === customVirtualFile) { + return `export const msg = "[success] from custom virtual file"` } } } diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index ffbf177bc7e935..e19ab1eea5fa51 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -32,9 +32,15 @@ export const FS_PREFIX = `/@fs/` export const VALID_ID_PREFIX = `/@id/` /** - * Some Rollup plugins use ids that starts with the null byte \0 to avoid - * collisions, but it is not permitted in import URLs so we have to replace - * them. + * Plugins that use 'virtual modules' (e.g. for helper functions), prefix the + * module ID with `\0`, a convention from the rollup ecosystem. + * This prevents other plugins from trying to process the id (like node resolution), + * and core features like sourcemaps can use this info to differentiate between + * virtual modules and regular files. + * `\0` is not a permitted char in import URLs so we have to replace them during + * import analysis. The id will be decoded back before entering the plugins pipeline. + * These encoded virtual ids are also prefixed by the VALID_ID_PREFIX, so virtual + * modules in the browser end up encoded as `/@id/__x00__{id}` */ export const NULL_BYTE_PLACEHOLDER = `__x00__` From dd757937884bb63dfc21ff38849e036aece056e8 Mon Sep 17 00:00:00 2001 From: patak-js Date: Sat, 13 Nov 2021 13:36:04 +0100 Subject: [PATCH 2/3] docs: note about SFC submodules convention --- docs/guide/api-plugin.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index e3f5df56a23a34..1b5fefff9805d1 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -38,6 +38,9 @@ If your plugin is only going to work for a particular framework, its name should Vite convention for virtual modules is to prefix the use facing path with `virtual:`. If possible the plugin name should be used as a namespace to avoid collisions with other plugins in the ecosystem. For example, a `vite-plugin-posts` could ask users to import a `virtual:posts` or `virtual:posts/helpers` virtual modules to get build time information. Internally, plugins that use virtual modules should prefix the module ID with `\0` while resolving the id, a convention from the rollup ecosystem. This prevents other plugins from trying to process the id (like node resolution), and core features like sourcemaps can use this info to differentiate between virtual modules and regular files. `\0` is not a permitted char in import URLs so we have to replace them during import analysis. A `\0{id}` virtual id ends up encoded as `/@id/__x00__{id}` during dev in the browser. The id will be decoded back before entering the plugins pipeline, so this is not seen by plugins hooks code. +Note that modules directly derived from a real file, as in the case of a script module in a Single File Component (like a .vue or .svelte SFC) don't need to follow this convention. SFCs generally generate a set of submodules when processed but the code in these can be mapped back to the filesystem. Using `\0` for these submodules would prevent sourcemaps from working correctly. + + ## Plugins config Users will add plugins to the project `devDependencies` and configure them using the `plugins` array option. @@ -86,28 +89,29 @@ It is common convention to author a Vite/Rollup plugin as a factory function tha ```js export default function myPlugin() { - const virtualFileId = '@my-virtual-file' + const virtualModuleId = '@my-virtual-module' + const resolvedVirtualModuleId = '\0' + virtualModuleId return { name: 'my-plugin', // required, will show up in warnings and errors resolveId(id) { - if (id === virtualFileId) { - return virtualFileId + if (id === virtualModuleId) { + return resolvedVirtualModuleId } }, load(id) { - if (id === virtualFileId) { - return `export const msg = "from virtual file"` + if (id === resolvedVirtualModuleId) { + return `export const msg = "from virtual module"` } } } } ``` -Which allows importing the file in JavaScript: +Which allows importing the module in JavaScript: ```js -import { msg } from '@my-virtual-file' +import { msg } from '@my-virtual-module' console.log(msg) ``` From 1724d80a519175dd8dde64e97be1dc1a2111e95c Mon Sep 17 00:00:00 2001 From: patak Date: Sat, 13 Nov 2021 15:45:38 +0100 Subject: [PATCH 3/3] chore: typo Co-authored-by: Bjorn Lu <34116392+bluwy@users.noreply.github.com> --- docs/guide/api-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index 1b5fefff9805d1..d8e1784d9dfb91 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -36,7 +36,7 @@ If your plugin is only going to work for a particular framework, its name should - `vite-plugin-react-` prefix for React Plugins - `vite-plugin-svelte-` prefix for Svelte Plugins -Vite convention for virtual modules is to prefix the use facing path with `virtual:`. If possible the plugin name should be used as a namespace to avoid collisions with other plugins in the ecosystem. For example, a `vite-plugin-posts` could ask users to import a `virtual:posts` or `virtual:posts/helpers` virtual modules to get build time information. Internally, plugins that use virtual modules should prefix the module ID with `\0` while resolving the id, a convention from the rollup ecosystem. This prevents other plugins from trying to process the id (like node resolution), and core features like sourcemaps can use this info to differentiate between virtual modules and regular files. `\0` is not a permitted char in import URLs so we have to replace them during import analysis. A `\0{id}` virtual id ends up encoded as `/@id/__x00__{id}` during dev in the browser. The id will be decoded back before entering the plugins pipeline, so this is not seen by plugins hooks code. +Vite convention for virtual modules is to prefix the user-facing path with `virtual:`. If possible the plugin name should be used as a namespace to avoid collisions with other plugins in the ecosystem. For example, a `vite-plugin-posts` could ask users to import a `virtual:posts` or `virtual:posts/helpers` virtual modules to get build time information. Internally, plugins that use virtual modules should prefix the module ID with `\0` while resolving the id, a convention from the rollup ecosystem. This prevents other plugins from trying to process the id (like node resolution), and core features like sourcemaps can use this info to differentiate between virtual modules and regular files. `\0` is not a permitted char in import URLs so we have to replace them during import analysis. A `\0{id}` virtual id ends up encoded as `/@id/__x00__{id}` during dev in the browser. The id will be decoded back before entering the plugins pipeline, so this is not seen by plugins hooks code. Note that modules directly derived from a real file, as in the case of a script module in a Single File Component (like a .vue or .svelte SFC) don't need to follow this convention. SFCs generally generate a set of submodules when processed but the code in these can be mapped back to the filesystem. Using `\0` for these submodules would prevent sourcemaps from working correctly.