@parcel/namer-default
Version:
184 lines (165 loc) • 5.79 kB
JavaScript
// @flow strict-local
import type {Bundle, FilePath} from '@parcel/types';
import {Namer} from '@parcel/plugin';
import ThrowableDiagnostic, {
convertSourceLocationToHighlight,
md,
} from '@parcel/diagnostic';
import assert from 'assert';
import path from 'path';
import nullthrows from 'nullthrows';
const COMMON_NAMES = new Set(['index', 'src', 'lib']);
const ALLOWED_EXTENSIONS = {
js: ['js', 'mjs', 'cjs'],
};
export default (new Namer({
name({bundle, bundleGraph}) {
let bundleGroup = bundleGraph.getBundleGroupsContainingBundle(bundle)[0];
let bundleGroupBundles = bundleGraph.getBundlesInBundleGroup(bundleGroup, {
includeInline: true,
});
let isEntry = bundleGraph.isEntryBundleGroup(bundleGroup);
if (bundle.needsStableName) {
let entryBundlesOfType = bundleGroupBundles.filter(
b => b.needsStableName && b.type === bundle.type,
);
assert(
entryBundlesOfType.length === 1,
// Otherwise, we'd end up naming two bundles the same thing.
`Bundle group cannot have more than one entry bundle of the same type. The offending bundle type is ${entryBundlesOfType[0].type}`,
);
}
let mainBundle = nullthrows(
bundleGroupBundles.find(b =>
b.getEntryAssets().some(a => a.id === bundleGroup.entryAssetId),
),
);
if (
bundle.id === mainBundle.id &&
isEntry &&
bundle.target &&
bundle.target.distEntry != null
) {
let loc = bundle.target.loc;
let distEntry = bundle.target.distEntry;
let distExtension = path.extname(bundle.target.distEntry).slice(1);
let allowedExtensions = ALLOWED_EXTENSIONS[bundle.type] || [bundle.type];
if (!allowedExtensions.includes(distExtension) && loc) {
let fullName = path.relative(
path.dirname(loc.filePath),
path.join(bundle.target.distDir, distEntry),
);
let err = new ThrowableDiagnostic({
diagnostic: {
message: md`Target "${bundle.target.name}" declares an output file path of "${fullName}" which does not match the compiled bundle type "${bundle.type}".`,
codeFrames: [
{
filePath: loc.filePath,
codeHighlights: [
convertSourceLocationToHighlight(
loc,
md`Did you mean "${
fullName.slice(0, -path.extname(fullName).length) +
'.' +
bundle.type
}"?`,
),
],
},
],
hints: [
`Try changing the file extension of "${
bundle.target.name
}" in ${path.relative(process.cwd(), loc.filePath)}.`,
],
},
});
throw err;
}
return bundle.target.distEntry;
}
// Base split bundle names on the first bundle in their group.
// e.g. if `index.js` imports `foo.css`, the css bundle should be called
// `index.css`.
let name = nameFromContent(
mainBundle,
bundle,
isEntry,
bundleGroup.entryAssetId,
bundleGraph.getEntryRoot(bundle.target),
);
if (!bundle.needsStableName) {
name += '.' + bundle.hashReference;
}
// Allow bundle extension to be overridden.
let extension = bundle.type;
let entry = bundle.getMainEntry();
if (entry && typeof entry.meta.bundleExtension === 'string') {
extension = entry.meta.bundleExtension;
}
// Group server and client bundles into separate folders.
// This allows users to easily upload to different places, and avoid exposing
// server code on public servers.
// If bundleExtension is set, assume everything is static (rendering at build time).
if (
(bundle.env.context === 'react-server' &&
entry?.meta?.bundleExtension == null) ||
(bundle.env.context === 'react-client' &&
hasReactServerEntries(bundleGraph))
) {
name = bundle.env.context.slice(6) + '/' + name;
}
return name + '.' + extension;
},
}): Namer);
function nameFromContent(
mainBundle: Bundle,
bundle: Bundle,
isEntry: boolean,
entryAssetId: string,
entryRoot: FilePath,
): string {
let entryFilePath = nullthrows(
mainBundle.getEntryAssets().find(a => a.id === entryAssetId),
).filePath;
let name = basenameWithoutExtension(entryFilePath);
// If this is an entry bundle, use the original relative path.
if (bundle.needsStableName) {
// Match name of target entry if possible, but with a different extension.
if (isEntry && bundle.target.distEntry != null) {
return basenameWithoutExtension(bundle.target.distEntry);
}
return path
.join(path.relative(entryRoot, path.dirname(entryFilePath)), name)
.replace(/\.\.(\/|\\)/g, 'up_$1');
} else {
// If this is an index file or common directory name, use the parent
// directory name instead, which is probably more descriptive.
while (COMMON_NAMES.has(name)) {
entryFilePath = path.dirname(entryFilePath);
name = path.basename(entryFilePath);
if (name.startsWith('.')) {
name = name.replace('.', '');
}
}
return name || 'bundle';
}
}
function basenameWithoutExtension(file) {
return path.basename(file, path.extname(file));
}
let rscEntryCache = new WeakMap();
function hasReactServerEntries(bundleGraph) {
let res = rscEntryCache.get(bundleGraph);
if (res == null) {
res = bundleGraph
.getEntryBundles()
.some(
b =>
b.env.context === 'react-server' &&
b.getMainEntry()?.meta?.bundleExtension == null,
);
rscEntryCache.set(bundleGraph, res);
}
return res;
}