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

feat: add base option for import.meta.glob #18510

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

hakshu25
Copy link

Description

fixes #17453.
Already #17625 is open, but it remained unresolved, so it was implemented.

@sapphi-red sapphi-red added enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority) labels Oct 30, 2024
Copy link
Member

@sapphi-red sapphi-red left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR!

docs/guide/features.md Outdated Show resolved Hide resolved
sapphi-red
sapphi-red previously approved these changes Nov 1, 2024
patak-dev
patak-dev previously approved these changes Nov 1, 2024
docs/guide/features.md Outdated Show resolved Hide resolved
```ts
// code produced by vite:
const moduleBase = {
'dir/foo.js': () => import('./dir/foo.js'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the key also start with ./ similar to without base?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right; it should definitely be consistent. I've made the changes.
7f0f5e7

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect the key to be without ./ as '**/*.js' does not have ./ at the head. I think this depends on how to describe the base option. If we describe this as a prefix string, then I think we should remove ./ from the key. If we describe this as a base path, then I think we should add ./ to the key. A prefix string is more powerful than base path because it allows non-path prefix and maybe it works well with aliases, but the base path feels more natural with the name base.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice this PR is indirectly supporting '**/*.js' when base is set. I was thinking more like the latter, a base path to resolve the glob from and it feels easier to visualize (for me), e.g. as if you're resolving the glob in a different file path.

I think both behaviours should be equally powerful and can do the same things. But if the behaviour is more like a prefix, then perhaps the option should be called prefix, but then someone could ask for suffix too.

I also think the behaviour could be easier implemented if treated as a base path, so instead of using the file dir or root (for virtual modules), we could use whatever passed by base instead.

Copy link
Member

@sapphi-red sapphi-red Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both behaviours should be equally powerful and can do the same things.

I think the prefix one is a bit more powerful. For the following files,

  • foo-bar/a.js
  • foo-baz/b.js

if the base behaves as prefix, you can write import.meta.glob('**/*.js', { base: 'foo-' })['bar/a.js']. But if the base behaves as base, you cannot write that and need to write import.meta.glob('./**/*.js', { base: './' })['foo-bar/a.js'].

That said, I'm fine with going with base-like behavior.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I was thinking in base-like and will keep the change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think we should keep base as a path and not as a prefix.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have modified the examples in the documentation to treat them as base instead of prefix.
500cb14

packages/vite/src/node/plugins/importMetaGlob.ts Outdated Show resolved Hide resolved
@hakshu25 hakshu25 dismissed stale reviews from patak-dev and sapphi-red via 1cd16f3 November 2, 2024 13:56
@hakshu25 hakshu25 requested a review from bluwy November 6, 2024 09:09
Copy link
Member

@bluwy bluwy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're treating base as the base path to resolve globs from, I think the implementation could be tweaked a bit as some still works a little like prefixes. I've made some comments below. Let me know if there's any parts unclear or would like help on.

Thanks for cleaning up the PR!

Comment on lines 578 to 586
const dir = importer ? globSafePath(dirname(importer)) : root
if (base) {
if (base.startsWith('./')) return pre + posix.join(dir, base, glob)
if (glob[0] === '@') {
const withoutAlias = /\/.+$/.exec(glob)?.[0]
return pre + posix.join(root, base, withoutAlias || glob)
}
}
glob = base ? posix.join(base, glob) : glob
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since base is now where we resolve globs from, I think what we should do instead is to update the dir here. For example, base should be used as dir if it's defined, otherwise we falllback to the existing logic. Like:

let dir
if (base) {
  if (base.startsWith('/')) { // root-relative
    dir = posix.join(root, base)
  } else { // assume relative path from the current file
    dir = posix.resolve(importer, base)
  }
} else {
  dir = importer ? globSafePath(dirname(importer)) : root
}

We shouldn't need to update or return the glob ourselves. That would be like prefixing.

This assumes that base can only be ./foo (relative to the current file) or /src/foo (relative from the root), which I think should be sufficient for an initial implementation. We might want to validate in parseGlobOption that it can only start with ./, ../. or /.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've made the changes below.
f6c657d

@@ -412,19 +419,35 @@ export async function transformGlobImport(

const resolvePaths = (file: string) => {
if (!dir) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific dir like my other comment may need to be updated too. In the code above it leads to:

const dir = isVirtual ? undefined : dirname(id)
const matches = await parseImportGlob(
code,
isVirtual ? undefined : id,
root,
resolveId,
logger,
)

It should also consider base like shown in my comment. However, we don't know about base until parseImportGlob() is called.

Maybe a refactor here could be to remove this dir entirely. parseImportGlob can return and we access from here instead:

matches.map(
async ({ globsResolved, isRelative, options, index, start, end }) => {

e.g.

async ({ globsResolved, isRelative, options, index, start, end, dir }) => {

Since we only need the dir within that callback. Free to explore other options to refactor this too though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to make the dir reusable. I'll try refactoring it.

@@ -412,19 +419,35 @@ export async function transformGlobImport(

const resolvePaths = (file: string) => {
if (!dir) {
if (isRelative)
if (!options.base && isRelative)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the updates only to dir in my comments, I believe we can then revert this part, from line 422 to 450, as I think it should automatically work then.

Comment on lines +77 to +79
export const aliasBase = import.meta.glob('@modules/*.ts', {
base: '/fixture-a/modules',
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To test this correctly, this resolveId function needs to have an alias feature supported.

const resolveId = (id) => {
  if (id.startsWith('@modules/')) {
    return normalizePath(path.resolve(root, 'modules', id.slice('@modules/'.length)))
  }
  return id
}

I expect the output to be same with

import.meta.glob('@modules/*.ts')

if the alias works like above.

I think we should also test:

import.meta.glob('./*.ts', {
  base: '@modules',
})

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect the output to be same with

import.meta.glob('@modules/*.ts')

You're right. I was actually suggesting a different output before (#18510 (comment)). Since base is now where we resolve the globs from, but aliases will always return a root-relative key, that might be one of the downsides if treating base this way I think. Unless we want to make base unique where, if it's specified, we'll resolve the keys relative to it?

I think we should also test:

import.meta.glob('./*.ts', {
  base: '@modules',
})

The implementation I had in mind when I added my reviews before is that base: '@modules' is a little tricky to support, and we can only support those that starts with / or ./ or ../. Since we're trying to figure out the dir to resolve from, and resolving alias would have to involve resolveId, but I don't think it handles resolving dirs well, only files.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're trying to figure out the dir to resolve from, and resolving alias would have to involve resolveId, but I don't think it handles resolving dirs well, only files.

We are passing glob to resolveId here.

const resolved = normalizePath(
(await resolveId(glob, importer, {
custom: { 'vite:import-glob': { isSubImportsPattern } },
})) || glob,
)

Maybe the dirs can be resoved by something like resolveId(base + '*').slice(0, -'*'.length)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah I guess if globs like @alias/*.ts can already be resolved here, then base should be able to do the same here too. Though if it's harder to get it right I was thinking it could be implemented later in the future. But fine by me either ways.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

base or cwd option for import.meta.glob
4 participants