Skip to content

Instantly share code, notes, and snippets.

@nuintun
Last active April 7, 2024 01:15
Show Gist options
  • Save nuintun/16a4df9a115da47c0166cb1d871f783e to your computer and use it in GitHub Desktop.
Save nuintun/16a4df9a115da47c0166cb1d871f783e to your computer and use it in GitHub Desktop.
rollup-import-meta-glob.ts
/**
* @module import-meta-glob
*/
import path from 'path';
import glob from 'fast-glob';
import { Plugin } from 'rollup';
import MagicString from 'magic-string';
import { stripLiteral } from 'strip-literal';
import acorn, { parseExpressionAt } from 'acorn';
import { normalizePath } from '@rollup/pluginutils';
interface ImportMatch {
ast: acorn.Node;
patterns: string[];
}
const IMPORT_GLOB_RE = /\bimport\.meta\.glob(?:<\w+>)?\s*\(/g;
function getLiteral(element: acorn.Literal): string {
const { value } = element;
if (typeof value !== 'string') {
throw new Error(`Expected glob to be a string but got ${typeof value}`);
}
return value;
}
function getMatches(code: string): ImportMatch[] {
const importMatches: ImportMatch[] = [];
const matches = stripLiteral(code).matchAll(IMPORT_GLOB_RE);
for (const match of matches) {
const ast = parseExpressionAt(code, match.index!, {
ranges: true,
sourceType: 'module',
ecmaVersion: 'latest'
});
if (ast.type === 'CallExpression') {
const args = ast.arguments;
// Only accept one argument
if (args.length !== 1) {
throw new Error('Expected 1 argument, but got ' + args.length);
}
const [argument] = args;
const patterns: string[] = [];
switch (argument.type) {
case 'Literal':
patterns.push(getLiteral(argument));
break;
case 'ArrayExpression':
const { elements } = argument;
for (const element of elements) {
if (element?.type === 'Literal') {
patterns.push(getLiteral(element));
} else {
throw new Error('Expected array argument to be a literal array');
}
}
break;
default:
throw new Error('Expected argument to be a literal or literal array');
}
importMatches.push({ ast, patterns });
}
}
return importMatches;
}
export default (): Plugin => {
return {
name: 'import-meta-glob',
async transform(code: string, id: string) {
if (code.includes('import.meta.glob')) {
const matches = getMatches(code);
const string = new MagicString(code);
for (const { ast, patterns } of matches) {
const imports: string[] = [];
for (const pattern of patterns) {
const files = await glob.async(pattern, {
dot: true,
absolute: true,
cwd: path.dirname(id)
});
for (const file of files) {
imports.push(`import('${normalizePath(file)}')`);
}
}
string.overwrite(ast.start, ast.end, `[${imports.join(',\n')}]`);
}
return string.toString();
}
}
};
};
@nuintun
Copy link
Author

nuintun commented Apr 2, 2024

declare interface ImportMeta {
  glob(path: string | string[]): Promise<object>[];
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment