utquidem
Version:
The meta-framework suite designed from scratch for frontend-focused modern web development.
156 lines (145 loc) • 4.29 kB
text/typescript
import fs from 'fs';
import path from 'path';
import { applyOptionsChain, findExists } from '@modern-js/utils';
import { NormalizedInputOptions, Plugin as RollupPlugin } from 'rollup';
import alias, { Alias } from '@rollup/plugin-alias';
import { createMatchPath, loadConfig, MatchPath } from 'tsconfig-paths';
import type { IAppContext, NormalizedConfig } from '@modern-js/core';
import {
DEV_CLIENT_PATH,
DEV_CLIENT_PATH_ALIAS,
BARE_SPECIFIER_REGEX,
DEFAULT_EXTENSIONS,
} from '../constants';
// webpack alias to rollup alias entries
export const normalizeAlias = (
config: NormalizedConfig,
appContext: IAppContext,
defaultDeps: string[],
) => {
const result: Alias[] = [
{
find: new RegExp(`^/?${appContext.internalDirAlias}/(.*)`),
replacement: `${appContext.internalDirectory}/$1`,
},
{
find: new RegExp(`^/?${appContext.internalSrcAlias}/(.*)`),
replacement: `${appContext.srcDirectory}/$1`,
},
{
find: new RegExp(`^\\/?${DEV_CLIENT_PATH_ALIAS}\\/(.*)`),
replacement: `${DEV_CLIENT_PATH}/$1`,
},
];
// merge default config & user config
const option = applyOptionsChain(
{
'@': './src',
'@shared': appContext.sharedDirectory,
},
config.source.alias as any,
);
Object.keys(option).forEach(key => {
const aliasPath = option[key];
const isAbsolute = path.isAbsolute(aliasPath);
// should exclude modern-js/runtime api
if (!defaultDeps.includes(key)) {
result.push({
find: key,
replacement: isAbsolute
? aliasPath
: `/${path.resolve(process.cwd(), aliasPath)}`,
});
}
});
return result;
};
export const aliasPlugin = (
config: NormalizedConfig,
appContext: IAppContext,
defaultDeps: string[],
): RollupPlugin => {
const aliasOptions = normalizeAlias(config, appContext, defaultDeps);
const plugin = alias({ entries: aliasOptions });
return {
name: 'esm-alias',
buildStart(options: NormalizedInputOptions) {
return plugin.buildStart?.call(this, options);
},
resolveId(id: string, importer?: string) {
return plugin.resolveId?.call(this, id, importer, {} as any);
},
};
};
/** Returns true when `path` is within `root` and not an installed dependency. */
const isLocalDescendant = (importer: string | undefined, root: string) => {
if (importer) {
return (
importer.startsWith(root) &&
!importer.slice(root.length).includes('/node_modules/')
);
} else {
return false;
}
};
export const tsAliasPlugin = (
config: NormalizedConfig,
appContext: IAppContext,
): RollupPlugin => {
const { appDirectory: root } = appContext;
const tsConfig: any = loadConfig(root);
let matchPath: MatchPath;
const { resultType } = tsConfig;
if (resultType === 'success' && tsConfig.paths) {
matchPath = createMatchPath(
tsConfig.absoluteBaseUrl,
tsConfig.paths,
tsConfig.mainFields || [
'module',
'jsnext',
'jsnext:main',
'browser',
'main',
],
tsConfig.addMatchAll,
);
}
const resolved = new Map<string, string>();
return {
name: 'esm-ts-alias',
resolveId(id, importer?: string) {
if (!BARE_SPECIFIER_REGEX.test(id)) {
return;
}
if (!matchPath) {
return;
}
let result = resolved.get(id);
if (!result && isLocalDescendant(importer, root)) {
result = matchPath(id, undefined, undefined, DEFAULT_EXTENSIONS);
if (result) {
result = path.extname(result)
? result
: (findExists(
DEFAULT_EXTENSIONS.map(ext => {
try {
const isDir = fs.statSync(result as string).isDirectory();
if (isDir) {
return path.resolve(
root,
`${result as string}/index${ext}`,
);
}
} catch (_err) {
// ignore err
}
return path.resolve(root, `${result as string}${ext}`);
}),
) as string);
resolved.set(id, result);
}
}
return result;
},
};
};