Skip to content

Commit

Permalink
feat: support resolveId returning arbitrary value
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 1, 2021
1 parent ac650c4 commit b782af4
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 51 deletions.
4 changes: 4 additions & 0 deletions packages/playground/resolve/__tests__/resolve.spec.ts
Expand Up @@ -29,3 +29,7 @@ test('css entry', async () => {
test('monorepo linked dep', async () => {
expect(await page.textContent('.monorepo')).toMatch('[success]')
})

test('plugin resolved virutal file', async () => {
expect(await page.textContent('.virtual')).toMatch('[success]')
})
6 changes: 6 additions & 0 deletions packages/playground/resolve/index.html
Expand Up @@ -24,6 +24,9 @@ <h2>CSS Entry</h2>
<h2>Monorepo linked dep</h2>
<p class="monorepo"></p>

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

<script type="module">
function text(selector, text) {
document.querySelector(selector).textContent = text
Expand Down Expand Up @@ -72,4 +75,7 @@ <h2>Monorepo linked dep</h2>
// monorepo linked dep w/ upper directory import
import { msg as linkedMsg } from 'resolve-linked'
text('.monorepo', linkedMsg)

import { msg as virtualMsg } from '@virtual-file'
text('.virtual', virtualMsg)
</script>
19 changes: 19 additions & 0 deletions packages/playground/resolve/vite.config.js
@@ -0,0 +1,19 @@
const virtualFile = '@virtual-file'

module.exports = {
plugins: [
{
name: 'custom-resolve',
resolveId(id) {
if (id === virtualFile) {
return id
}
},
load(id) {
if (id === virtualFile) {
return `export const msg = "[success] from virtual file"`
}
}
}
]
}
5 changes: 5 additions & 0 deletions packages/vite/src/node/constants.ts
Expand Up @@ -9,6 +9,11 @@ export const DEP_CACHE_DIR = `.vite`
*/
export const FS_PREFIX = `/@fs/`

/**
* Prefix for resolved Ids that are not valid browser import specifiers
*/
export const VALID_ID_PREFIX = `/@id/`

export const CLIENT_PUBLIC_PATH = `/@vite/client`
// eslint-disable-next-line
export const CLIENT_ENTRY = require.resolve('vite/dist/client/client.js')
Expand Down
14 changes: 13 additions & 1 deletion packages/vite/src/node/plugins/importsAnalysis.ts
Expand Up @@ -23,7 +23,12 @@ import {
handlePrunedModules,
lexAcceptedHmrDeps
} from '../server/hmr'
import { FS_PREFIX, CLIENT_PUBLIC_PATH, DEP_VERSION_RE } from '../constants'
import {
FS_PREFIX,
CLIENT_PUBLIC_PATH,
DEP_VERSION_RE,
VALID_ID_PREFIX
} from '../constants'
import { ViteDevServer } from '../'
import { checkPublicFile } from './asset'
import { parse as parseJS } from 'acorn'
Expand Down Expand Up @@ -236,6 +241,13 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
}
}

// if the resolved id is not a valid browser import specifier,
// prefix it to make it valid. We will strip this before feeding it
// back into the transform pipeline
if (!url.startsWith('.') && !url.startsWith('/')) {
url = VALID_ID_PREFIX + resolved.id
}

// for relative imports, inherit importer's version query
if (isRelative) {
const versionMatch = importer.match(DEP_VERSION_RE)
Expand Down
97 changes: 48 additions & 49 deletions packages/vite/src/node/plugins/resolve.ts
Expand Up @@ -48,10 +48,9 @@ export function resolvePlugin(

resolveId(id, importer) {
let res

// explicit fs paths that starts with /@fs/*
if (asSrc && id.startsWith(FS_PREFIX)) {
// explicit fs paths that starts with /@fs/*
// these are injected by the rewrite plugin so that the file can work
// in the browser
let fsPath = id.slice(FS_PREFIX.length - 1)
if (fsPath.startsWith('//')) fsPath = fsPath.slice(1)
res = tryFsResolve(fsPath, false)
Expand Down Expand Up @@ -181,56 +180,56 @@ export function tryNodeResolve(
const pkgId = deepMatch ? deepMatch[1] || deepMatch[2] : id
const pkg = resolvePackageData(pkgId, basedir)

if (pkg) {
// prevent deep imports to optimized deps.
if (
deepMatch &&
server &&
server.optimizeDepsMetadata &&
pkg.data.name in server.optimizeDepsMetadata.map
) {
throw new Error(
chalk.yellow(
`Deep import "${chalk.cyan(
id
)}" should be avoided because dependency "${chalk.cyan(
pkg.data.name
)}" has been pre-optimized. Prefer importing directly from the module entry:\n\n` +
`${chalk.green(`import { ... } from "${pkg.data.name}"`)}\n\n` +
`If the used import is not exported from the package's main entry ` +
`and can only be attained via deep import, you can explicitly add ` +
`the deep import path to "optimizeDeps.include" in vite.config.js.`
)
if (!pkg) {
return
}

// prevent deep imports to optimized deps.
if (
deepMatch &&
server &&
server.optimizeDepsMetadata &&
pkg.data.name in server.optimizeDepsMetadata.map
) {
throw new Error(
chalk.yellow(
`Deep import "${chalk.cyan(
id
)}" should be avoided because dependency "${chalk.cyan(
pkg.data.name
)}" has been pre-optimized. Prefer importing directly from the module entry:\n\n` +
`${chalk.green(`import { ... } from "${pkg.data.name}"`)}\n\n` +
`If the used import is not exported from the package's main entry ` +
`and can only be attained via deep import, you can explicitly add ` +
`the deep import path to "optimizeDeps.include" in vite.config.js.`
)
}
)
}

let resolved = deepMatch
? resolveDeepImport(id, pkg)
: resolvePackageEntry(id, pkg)
if (!resolved) {
return
}
// link id to pkg for browser field mapping check
idToPkgMap.set(resolved, pkg)
if (isBuild) {
// Resolve package side effects for build so that rollup can better
// perform tree-shaking
return {
id: resolved,
moduleSideEffects: pkg.hasSideEffects(resolved)
}
} else {
// During serve, inject a version query to npm deps so that the browser
// can cache it without revalidation. Make sure to apply this only to
// files actually inside node_modules so that locally linked packages
// in monorepos are not cached this way.
if (resolved.includes('node_modules')) {
resolved = injectQuery(resolved, `v=${pkg.data.version}`)
}
return { id: resolved }
let resolved = deepMatch
? resolveDeepImport(id, pkg)
: resolvePackageEntry(id, pkg)
if (!resolved) {
return
}
// link id to pkg for browser field mapping check
idToPkgMap.set(resolved, pkg)
if (isBuild) {
// Resolve package side effects for build so that rollup can better
// perform tree-shaking
return {
id: resolved,
moduleSideEffects: pkg.hasSideEffects(resolved)
}
} else {
throw new Error(`Failed to resolve package.json for module "${id}"`)
// During serve, inject a version query to npm deps so that the browser
// can cache it without revalidation. Make sure to apply this only to
// files actually inside node_modules so that locally linked packages
// in monorepos are not cached this way.
if (resolved.includes('node_modules')) {
resolved = injectQuery(resolved, `v=${pkg.data.version}`)
}
return { id: resolved }
}
}

Expand Down
8 changes: 7 additions & 1 deletion packages/vite/src/node/server/middlewares/transform.ts
Expand Up @@ -15,7 +15,7 @@ import { send } from '../send'
import { transformRequest } from '../transformRequest'
import { isHTMLProxy } from '../../plugins/html'
import chalk from 'chalk'
import { DEP_CACHE_DIR, DEP_VERSION_RE } from '../../constants'
import { DEP_CACHE_DIR, DEP_VERSION_RE, VALID_ID_PREFIX } from '../../constants'

const debugCache = createDebugger('vite:cache')
const isDebug = !!process.env.DEBUG
Expand Down Expand Up @@ -79,6 +79,12 @@ export function transformMiddleware(
// strip ?import
url = removeImportQuery(url)

// Strip valid id prefix. This is preprended to resolved Ids that are
// not valid browser import specifiers by the importAnalysis plugin.
if (url.startsWith(VALID_ID_PREFIX)) {
url = url.slice(VALID_ID_PREFIX.length)
}

// for CSS, we need to differentiate between normal CSS requests and
// imports
if (isCSSRequest(url) && req.headers.accept?.includes('text/css')) {
Expand Down

0 comments on commit b782af4

Please sign in to comment.