Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: virtual modules internal convention #5553

Merged
merged 3 commits into from Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 2 additions & 4 deletions docs/guide/api-plugin.md
Expand Up @@ -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.
Expand All @@ -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.
patak-dev marked this conversation as resolved.
Show resolved Hide resolved

## Plugins config

Expand Down
4 changes: 4 additions & 0 deletions packages/playground/resolve/__tests__/resolve.spec.ts
Expand Up @@ -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]')
})
Expand Down
6 changes: 6 additions & 0 deletions packages/playground/resolve/index.html
Expand Up @@ -53,6 +53,9 @@ <h2>Monorepo linked dep</h2>
<h2>Plugin resolved virtual file</h2>
<p class="virtual"></p>

<h2>Plugin resolved custom virtual file</h2>
<p class="custom-virtual"></p>

<h2>Inline package</h2>
<p class="inline-pkg"></p>

Expand Down Expand Up @@ -180,6 +183,9 @@ <h2>resolve package that contains # in path</h2>
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)

Expand Down
22 changes: 19 additions & 3 deletions 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: {
Expand All @@ -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"`
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions packages/vite/src/node/constants.ts
Expand Up @@ -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__`

Expand Down