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

Unified file emission api #2999

Merged
merged 30 commits into from Aug 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8beb5f6
Use new FileEmitter for basic cases around assets. TODO: Replace asset
lukastaegert Jul 5, 2019
c27680d
Migrate assets to new file emitter
lukastaegert Jul 7, 2019
be510fe
Remove assetsById from Graph
lukastaegert Jul 7, 2019
2825885
Implement emitFile for assets
lukastaegert Jul 7, 2019
aeb81ff
Internally use EmittedFile in the file emitter
lukastaegert Jul 8, 2019
2d9e8fd
Deprecate emitAsset and ROLLUP_ASSET_URL
lukastaegert Jul 10, 2019
022f25b
Deprecate getAssetFileName
lukastaegert Jul 10, 2019
a6c0e17
Merge chunk emission into unified API and deprecated previous API
lukastaegert Jul 12, 2019
f5ad4b1
Allow emitting files with fixed names
lukastaegert Jul 13, 2019
1b25216
Support unnamed assets
lukastaegert Jul 13, 2019
e027bbd
Improve chunk name assignment
lukastaegert Jul 17, 2019
8216cfc
Initial support for chunk file names
lukastaegert Jul 19, 2019
270eb5b
Allow specifying explicit file names for emitted chunks
lukastaegert Jul 20, 2019
5e85eee
Fix some TODOs
lukastaegert Jul 21, 2019
c7bfaee
Test ids remain stable when the transform hook is cached and make test
lukastaegert Jul 21, 2019
c90026a
Refine error handling
lukastaegert Jul 22, 2019
4087a23
Test some more errors
lukastaegert Jul 22, 2019
39fb3a4
Refine file emission
lukastaegert Jul 23, 2019
d583068
Refactor file emitter to have a single code path for asset finalization
lukastaegert Jul 24, 2019
a2316a5
Deduplicated emitted assets without a specific file name
lukastaegert Jul 24, 2019
0972501
Only use the alias as name for a manual chunk if the chunk is not facade
lukastaegert Jul 30, 2019
5078443
Generate separate facades for duplicate named user-defined entry points
lukastaegert Jul 31, 2019
689ba18
Always create facades for explicit file names
lukastaegert Aug 1, 2019
0bf918a
Test edge cases
lukastaegert Aug 1, 2019
fedee4d
Test and refactor handling of dynamic relative imports
lukastaegert Aug 2, 2019
b3cd734
Use async-await in generate function, remove error condition
lukastaegert Aug 2, 2019
56b0a06
Improve and test pattern validation
lukastaegert Aug 2, 2019
35ecf88
Test file emitter edge cases
lukastaegert Aug 2, 2019
6d4199e
Improve plugin error handling
lukastaegert Aug 2, 2019
e4f44c0
Add documentation
lukastaegert Aug 2, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
131 changes: 81 additions & 50 deletions docs/05-plugin-development.md

Large diffs are not rendered by default.

201 changes: 114 additions & 87 deletions src/Chunk.ts
Expand Up @@ -77,6 +77,11 @@ export interface ImportSpecifier {
local: string;
}

interface FacadeName {
fileName?: string;
name?: string;
}

function getGlobalName(
module: ExternalModule,
globals: GlobalsOption,
Expand Down Expand Up @@ -110,12 +115,19 @@ export function isChunkRendered(chunk: Chunk): boolean {
}

export default class Chunk {
static generateFacade(graph: Graph, facadedModule: Module): Chunk {
private static generateFacade(
graph: Graph,
facadedModule: Module,
facadeName: FacadeName
): Chunk {
const chunk = new Chunk(graph, []);
chunk.assignFacadeName(facadeName, facadedModule);
if (!facadedModule.facadeChunk) {
facadedModule.facadeChunk = chunk;
}
chunk.dependencies = [facadedModule.chunk as Chunk];
chunk.dynamicDependencies = [];
chunk.facadeModule = facadedModule;
facadedModule.facadeChunk = chunk;
for (const exportName of facadedModule.getAllExportNames()) {
const tracedVariable = facadedModule.getVariableForExportName(exportName);
chunk.exports.add(tracedVariable);
Expand All @@ -129,7 +141,7 @@ export default class Chunk {
exportMode: 'none' | 'named' | 'default' = 'named';
facadeModule: Module | null = null;
graph: Graph;
id: string = undefined as any;
id: string | null = null;
indentString: string = undefined as any;
isEmpty: boolean;
manualChunkAlias: string | null = null;
Expand All @@ -138,14 +150,15 @@ export default class Chunk {
[moduleId: string]: RenderedModule;
};
usedModules: Module[] = undefined as any;
variableName = 'chunk';

variableName: string;
private chunkName?: string;
private dependencies: (ExternalModule | Chunk)[] = undefined as any;
private dynamicDependencies: (ExternalModule | Chunk)[] = undefined as any;
private exportNames: { [name: string]: Variable } = Object.create(null);
private exports = new Set<Variable>();
private fileName: string | null = null;
private imports = new Set<Variable>();
private name: string | null = null;
private needsExportsShim = false;
private renderedDeclarations: {
dependencies: ChunkDependencies;
Expand Down Expand Up @@ -179,25 +192,20 @@ export default class Chunk {
}
}

const entryModule = this.entryModules[0];
if (entryModule) {
const moduleForNaming =
this.entryModules[0] || this.orderedModules[this.orderedModules.length - 1];
if (moduleForNaming) {
this.variableName = makeLegal(
basename(
entryModule.chunkAlias || entryModule.manualChunkAlias || getAliasName(entryModule.id)
moduleForNaming.chunkName ||
moduleForNaming.manualChunkAlias ||
getAliasName(moduleForNaming.id)
)
);
} else {
this.variableName = '__chunk_' + ++graph.curChunkIndex;
}
}

canModuleBeFacade(
moduleExportNamesByVariable: Map<Variable, string[]>,
moduleChunkAlias: string | null
): boolean {
if (this.manualChunkAlias && moduleChunkAlias && this.manualChunkAlias !== moduleChunkAlias) {
return false;
}
canModuleBeFacade(moduleExportNamesByVariable: Map<Variable, string[]>): boolean {
for (const exposedVariable of this.exports) {
if (!moduleExportNamesByVariable.has(exposedVariable)) {
return false;
Expand All @@ -209,22 +217,33 @@ export default class Chunk {
generateFacades(): Chunk[] {
const facades: Chunk[] = [];
for (const module of this.entryModules) {
const requiredFacades: FacadeName[] = Array.from(module.userChunkNames).map(name => ({
name
}));
if (requiredFacades.length === 0 && module.isUserDefinedEntryPoint) {
requiredFacades.push({});
}
requiredFacades.push(...Array.from(module.chunkFileNames).map(fileName => ({ fileName })));
if (requiredFacades.length === 0) {
requiredFacades.push({});
}
if (!this.facadeModule) {
const exportNamesByVariable = module.getExportNamesByVariable();
if (
this.graph.preserveModules ||
this.canModuleBeFacade(exportNamesByVariable, module.chunkAlias)
) {
if (this.graph.preserveModules || this.canModuleBeFacade(exportNamesByVariable)) {
this.facadeModule = module;
module.facadeChunk = this;
for (const [variable, exportNames] of exportNamesByVariable) {
for (const exportName of exportNames) {
this.exportNames[exportName] = variable;
}
}
continue;
this.assignFacadeName(requiredFacades.shift() as FacadeName, module);
}
}
facades.push(Chunk.generateFacade(this.graph, module));

for (const facadeName of requiredFacades) {
facades.push(Chunk.generateFacade(this.graph, module, facadeName));
}
}
return facades;
}
Expand All @@ -234,30 +253,27 @@ export default class Chunk {
patternName: string,
addons: Addons,
options: OutputOptions,
existingNames: Record<string, true>
) {
this.id = makeUnique(
renderNamePattern(pattern, patternName, type => {
switch (type) {
case 'format':
return options.format === 'es' ? 'esm' : options.format;
case 'hash':
return this.computeContentHashWithDependencies(addons, options);
case 'name':
return this.getChunkName();
}
return undefined as any;
existingNames: Record<string, any>
): string {
if (this.fileName !== null) {
return this.fileName;
}
return makeUnique(
renderNamePattern(pattern, patternName, {
format: () => (options.format === 'es' ? 'esm' : (options.format as string)),
hash: () => this.computeContentHashWithDependencies(addons, options),
name: () => this.getChunkName()
}),
existingNames
);
}

generateIdPreserveModules(
preserveModulesRelativeDir: string,
existingNames: Record<string, true>
) {
existingNames: Record<string, any>
): string {
const sanitizedId = sanitizeFileName(this.orderedModules[0].id);
this.id = makeUnique(
return makeUnique(
normalize(
isAbsolute(this.orderedModules[0].id)
? relative(preserveModulesRelativeDir, sanitizedId)
Expand Down Expand Up @@ -304,11 +320,11 @@ export default class Chunk {
}

getChunkName(): string {
return this.chunkName || (this.chunkName = this.computeChunkName());
return this.name || (this.name = sanitizeFileName(this.getFallbackChunkName()));
}

getDynamicImportIds(): string[] {
return this.dynamicDependencies.map(chunk => chunk.id).filter(Boolean);
return this.dynamicDependencies.map(chunk => chunk.id).filter(Boolean) as string[];
}

getExportNames(): string[] {
Expand All @@ -318,7 +334,7 @@ export default class Chunk {
}

getImportIds(): string[] {
return this.dependencies.map(chunk => chunk.id);
return this.dependencies.map(chunk => chunk.id).filter(Boolean) as string[];
}

getRenderedHash(): string {
Expand Down Expand Up @@ -616,13 +632,9 @@ export default class Chunk {
if (dep instanceof ExternalModule && !dep.renormalizeRenderPath) continue;

const renderedDependency = this.renderedDeclarations.dependencies[i];

const depId = dep instanceof ExternalModule ? renderedDependency.id : dep.id;
let relPath = this.id ? normalize(relative(dirname(this.id), depId)) : depId;
if (!relPath.startsWith('../')) relPath = './' + relPath;

const depId = dep instanceof ExternalModule ? renderedDependency.id : (dep.id as string);
if (dep instanceof Chunk) renderedDependency.namedExportsMode = dep.exportMode !== 'default';
renderedDependency.id = relPath;
renderedDependency.id = this.getRelativePath(depId);
}

this.finaliseDynamicImports(format);
Expand Down Expand Up @@ -697,23 +709,18 @@ export default class Chunk {

let file: string;
if (options.file) file = resolve(options.sourcemapFile || options.file);
else if (options.dir) file = resolve(options.dir, this.id);
else file = resolve(this.id);

if (this.graph.pluginDriver.hasLoadersOrTransforms) {
const decodedMap = magicString.generateDecodedMap({});
map = collapseSourcemaps(
this,
file,
decodedMap,
this.usedModules,
chunkSourcemapChain,
options.sourcemapExcludeSources as boolean
);
} else {
map = magicString.generateMap({ file, includeContent: !options.sourcemapExcludeSources });
}

else if (options.dir) file = resolve(options.dir, this.id as string);
else file = resolve(this.id as string);

const decodedMap = magicString.generateDecodedMap({});
map = collapseSourcemaps(
this,
file,
decodedMap,
this.usedModules,
chunkSourcemapChain,
options.sourcemapExcludeSources as boolean
);
map.sources = map.sources.map(sourcePath =>
normalize(
options.sourcemapPathTransform ? options.sourcemapPathTransform(sourcePath) : sourcePath
Expand Down Expand Up @@ -782,17 +789,14 @@ export default class Chunk {
}
}

private computeChunkName(): string {
if (this.manualChunkAlias) {
return sanitizeFileName(this.manualChunkAlias);
}
if (this.facadeModule !== null) {
return sanitizeFileName(this.facadeModule.chunkAlias || getAliasName(this.facadeModule.id));
}
for (const module of this.orderedModules) {
if (module.chunkAlias) return sanitizeFileName(module.chunkAlias);
private assignFacadeName({ fileName, name }: FacadeName, facadedModule: Module) {
if (fileName) {
this.fileName = fileName;
} else {
this.name = sanitizeFileName(
name || facadedModule.chunkName || getAliasName(facadedModule.id)
);
}
return 'chunk';
}

private computeContentHashWithDependencies(addons: Addons, options: OutputOptions): string {
Expand All @@ -817,19 +821,24 @@ export default class Chunk {
if (resolution instanceof Module) {
if (resolution.chunk !== this && isChunkRendered(resolution.chunk as Chunk)) {
const resolutionChunk = resolution.facadeChunk || (resolution.chunk as Chunk);
let relPath = normalize(relative(dirname(this.id), resolutionChunk.id));
if (!relPath.startsWith('../')) relPath = './' + relPath;
node.renderFinalResolution(code, `'${relPath}'`, format);
node.renderFinalResolution(
code,
`'${this.getRelativePath(resolutionChunk.id as string)}'`,
format
);
}
} else if (resolution instanceof ExternalModule) {
let resolutionId = resolution.id;
if (resolution.renormalizeRenderPath) {
resolutionId = normalize(relative(dirname(this.id), resolution.renderPath));
if (!resolutionId.startsWith('../')) resolutionId = './' + resolutionId;
}
node.renderFinalResolution(code, `'${resolutionId}'`, format);
} else {
node.renderFinalResolution(code, resolution, format);
node.renderFinalResolution(
code,
resolution instanceof ExternalModule
? `'${
resolution.renormalizeRenderPath
? this.getRelativePath(resolution.renderPath)
: resolution.id
}'`
: resolution,
format
);
}
}
}
Expand All @@ -838,7 +847,7 @@ export default class Chunk {
private finaliseImportMetas(format: string): void {
for (const [module, code] of this.renderedModuleSources) {
for (const importMeta of module.importMetas) {
importMeta.renderFinalMechanism(code, this.id, format, this.graph.pluginDriver);
importMeta.renderFinalMechanism(code, this.id as string, format, this.graph.pluginDriver);
}
}
}
Expand Down Expand Up @@ -983,6 +992,24 @@ export default class Chunk {
return exports;
}

private getFallbackChunkName(): string {
if (this.manualChunkAlias) {
return this.manualChunkAlias;
}
if (this.fileName) {
return getAliasName(this.fileName);
}
for (const module of this.orderedModules) {
if (module.chunkName) return module.chunkName;
}
return 'chunk';
}

private getRelativePath(targetPath: string): string {
const relativePath = normalize(relative(dirname(this.id as string), targetPath));
return relativePath.startsWith('../') ? relativePath : './' + relativePath;
}

private inlineChunkDependencies(chunk: Chunk, deep: boolean) {
for (const dep of chunk.dependencies) {
if (dep instanceof ExternalModule) {
Expand Down