Skip to content
Permalink
Browse files
Handle caching and invalidation of assets (#2267)
  • Loading branch information
lukastaegert committed Jun 27, 2018
1 parent c3228cd commit ad41348e9f4f23fb2a8381bc67730c77b8381b23
Showing 9 changed files with 286 additions and 64 deletions.
@@ -9,6 +9,7 @@ import Chunk from './Chunk';
import ExternalModule from './ExternalModule';
import Module, { defaultAcornOptions } from './Module';
import {
Asset,
InputOptions,
IsExternal,
LoadHook,
@@ -25,7 +26,7 @@ import {
WarningHandler,
Watcher
} from './rollup/types';
import { Asset, createAssetPluginHooks, finaliseAsset } from './utils/assetHooks';
import { createAssetPluginHooks, EmitAsset, finaliseAsset } from './utils/assetHooks';
import { load, makeOnwarn, resolveId } from './utils/defaults';
import ensureArray from './utils/ensureArray';
import {
@@ -68,6 +69,8 @@ export default class Graph {
treeshakingOptions: TreeshakingOptions;
varOrConst: 'var' | 'const';

private createTransformEmitAsset: () => { assets: Asset[]; emitAsset: EmitAsset };

contextParse: (code: string, acornOptions?: acorn.Options) => Program;

// deprecated
@@ -120,6 +123,8 @@ export default class Graph {
return this.acornParse(code, { ...defaultAcornOptions, ...options, ...this.acornOptions });
};

const assetPluginHooks = createAssetPluginHooks(this.assetsById);

this.pluginContext = {
watcher,
isExternal: undefined,
@@ -133,8 +138,11 @@ export default class Graph {
if (typeof err === 'string') throw new Error(err);
error(err);
},
...createAssetPluginHooks(this.assetsById)
emitAsset: assetPluginHooks.emitAsset,
getAssetFileName: assetPluginHooks.getAssetFileName,
setAssetSource: assetPluginHooks.setAssetSource
};
this.createTransformEmitAsset = assetPluginHooks.createTransformEmitAsset;

this.resolveId = first(
[
@@ -215,8 +223,16 @@ export default class Graph {
}

getCache() {
const assetDependencies: string[] = [];
this.assetsById.forEach(asset => {
if (!asset.transform && asset.dependencies && asset.dependencies.length) {
for (const depId of asset.dependencies) assetDependencies.push(depId);
}
});

return {
modules: this.modules.map(module => module.toJSON())
modules: this.modules.map(module => module.toJSON()),
assetDependencies
};
}

@@ -666,14 +682,24 @@ Try defining "${chunkName}" first in the manualChunks definitions of the Rollup
}
: source;

if (
this.cachedModules.has(id) &&
this.cachedModules.get(id).originalCode === sourceDescription.code
) {
return this.cachedModules.get(id);
const cachedModule = this.cachedModules.get(id);
if (cachedModule && cachedModule.originalCode === sourceDescription.code) {
// re-emit transform assets
if (cachedModule.transformAssets) {
for (const asset of cachedModule.transformAssets) {
this.pluginContext.emitAsset(asset.name);
}
}
return cachedModule;
}

return transform(this, sourceDescription, id, this.plugins);
return transform(
this,
sourceDescription,
module,
this.plugins,
this.createTransformEmitAsset
);
})
.then((source: ModuleJSON) => {
module.setSource(source);
@@ -29,7 +29,7 @@ import Variable from './ast/variables/Variable';
import Chunk from './Chunk';
import ExternalModule from './ExternalModule';
import Graph from './Graph';
import { IdMap, ModuleJSON, RawSourceMap, RollupError, RollupWarning } from './rollup/types';
import { Asset, IdMap, ModuleJSON, RawSourceMap, RollupError, RollupWarning } from './rollup/types';
import { handleMissingExport } from './utils/defaults';
import error from './utils/error';
import getCodeFrame from './utils/getCodeFrame';
@@ -169,6 +169,7 @@ export default class Module {
alias: string;
resolution: Module | ExternalModule | string | void;
}[];
transformAssets: Asset[];

execIndex: number;
isEntryPoint: boolean;
@@ -646,6 +647,7 @@ export default class Module {
id: this.id,
dependencies: this.dependencies.map(module => module.id),
transformDependencies: this.transformDependencies,
transformAssets: this.transformAssets,
code: this.code,
originalCode: this.originalCode,
originalSourcemap: this.originalSourcemap,
@@ -25,17 +25,17 @@ export interface RollupError {
pluginCode?: string;
}

export type RawSourceMap =
| { mappings: '' }
| {
version: string;
sources: string[];
names: string[];
sourceRoot?: string;
sourcesContent?: string[];
mappings: string;
file: string;
};
export interface ExistingRawSourceMap {
version: string;
sources: string[];
names: string[];
sourceRoot?: string;
sourcesContent?: string[];
mappings: string;
file: string;
}

export type RawSourceMap = { mappings: '' } | ExistingRawSourceMap;

export interface SourceMap {
version: string;
@@ -52,13 +52,18 @@ export interface SourceMap {
export interface SourceDescription {
code: string;
map?: string | RawSourceMap;
}

export interface TransformSourceDescription extends SourceDescription {
ast?: ESTree.Program;
dependencies?: string[];
}

export interface ModuleJSON {
id: string;
dependencies: string[];
transformDependencies: string[];
transformAssets: Asset[] | void;
code: string;
originalCode: string;
originalSourcemap: RawSourceMap | void;
@@ -67,12 +72,21 @@ export interface ModuleJSON {
resolvedIds: IdMap;
}

export interface Asset {
name: string;
source: string | Buffer;
fileName: string;
transform: boolean;
dependencies: string[];
}

export interface PluginContext {
watcher: Watcher;
resolveId: ResolveIdHook;
isExternal: IsExternal;
parse: (input: string, options: any) => ESTree.Program;
emitAsset: (name: string, source?: string | Buffer) => string;
emitAsset(name: string, source?: string | Buffer): string;
emitAsset(name: string, dependencies: string[], source?: string | Buffer): string;
setAssetSource: (assetId: string, source: string | Buffer) => void;
getAssetFileName: (assetId: string) => string;
warn(warning: RollupWarning | string, pos?: { line: number; column: number }): void;
@@ -100,7 +114,11 @@ export type TransformHook = (
this: PluginContext,
code: string,
id: string
) => Promise<SourceDescription | string | void> | SourceDescription | string | void;
) =>
| Promise<TransformSourceDescription | string | void>
| TransformSourceDescription
| string
| void;

export type TransformChunkHook = (
code: string,
@@ -325,6 +343,7 @@ export interface OutputChunk {

export interface RollupCache {
modules: ModuleJSON[];
assetDependencies: string[];
}

export interface RollupSingleFileBuild {
@@ -1,16 +1,15 @@
import sha256 from 'hash.js/lib/hash/sha/256';
import { OutputBundle } from '../rollup/types';
import { randomHexString } from './entryHashing';
import { Asset, OutputBundle } from '../rollup/types';
import error from './error';
import { extname } from './path';
import { extname, normalize, resolve } from './path';
import { isPlainName } from './relativeId';
import { makeUnique, renderNamePattern } from './renderNamePattern';

export interface Asset {
name: string;
source: string | Buffer;
fileName: string;
}
export type EmitAsset = (
name: string,
dependencies?: string[] | string | Buffer,
source?: string | Buffer
) => string;

export function getAssetFileName(
asset: Asset,
@@ -51,18 +50,64 @@ export function createAssetPluginHooks(
outputBundle?: OutputBundle,
assetFileNames?: string
) {
return {
emitAsset(name: string, source?: string | Buffer) {
if (typeof name !== 'string' || !isPlainName(name))
function emitAsset(
name: string,
dependenciesOrSource?: string[] | string | Buffer,
source?: string | Buffer
) {
if (typeof name !== 'string' || !isPlainName(name))
error({
code: 'INVALID_ASSET_NAME',
message: `Plugin error creating asset, name is not a plain (non relative or absolute URL) string name.`
});

let dependencies: string[];

if (Array.isArray(dependenciesOrSource)) {
if (outputBundle)
error({
code: 'INVALID_ASSET_NAME',
message: `Plugin error creating asset, name is not a plain (non relative or absolute URL) string name.`
code: 'ASSETS_FINALISED',
message: `Plugin error creating asset, asset dependencies are not supported during generation, only during the build.`
});
const assetId = randomHexString(8);
const asset: Asset = { name, source, fileName: undefined };
if (outputBundle && source !== undefined) finaliseAsset(asset, outputBundle, assetFileNames);
assetsById.set(assetId, asset);
return assetId;
dependencies = dependenciesOrSource.map(depId => normalize(resolve(depId)));
} else if (source === undefined) {
source = dependenciesOrSource;
}

let assetId: string;
do {
const assetHash = sha256();
if (assetId) {
// if there is a collision, chain until there isn't
assetHash.update(assetId);
} else {
assetHash.update(name);
if (dependencies) for (const depId of dependencies) assetHash.update(depId);
}
assetId = assetHash.digest('hex').substr(0, 8);
} while (assetsById.has(assetId));

const asset: Asset = { name, source, fileName: undefined, dependencies, transform: false };
if (outputBundle && source !== undefined) finaliseAsset(asset, outputBundle, assetFileNames);
assetsById.set(assetId, asset);
return assetId;
}

return {
emitAsset,
createTransformEmitAsset() {
const assets: Asset[] = [];
return {
assets,
emitAsset: (name: string, dependencies?: string[], source?: string | Buffer) => {
const assetId = emitAsset(name, dependencies, source);
const asset = assetsById.get(assetId);
// distinguish transform assets
asset.transform = true;
assets.push(asset);
return assetId;
}
};
},
setAssetSource: (assetId: string, source: string | Buffer) => {
const asset = assetsById.get(assetId);
@@ -1,7 +1,7 @@
import { DecodedSourceMap, SourceMap } from 'magic-string';
import Chunk from '../Chunk';
import Module from '../Module';
import { RawSourceMap } from '../rollup/types';
import { ExistingRawSourceMap, RawSourceMap } from '../rollup/types';
import error from './error';
import { basename, dirname, relative, resolve } from './path';

@@ -146,25 +146,26 @@ export default function collapseSourcemaps(
let sourcemapChain = module.sourcemapChain;

let source: Source;
if (!module.originalSourcemap) {
const originalSourcemap = <ExistingRawSourceMap>module.originalSourcemap;
if (!originalSourcemap) {
source = new Source(module.id, module.originalCode);
} else {
const sources = module.originalSourcemap.sources;
const sourcesContent = module.originalSourcemap.sourcesContent || [];
const sources = originalSourcemap.sources;
const sourcesContent = originalSourcemap.sourcesContent || [];

if (sources == null || (sources.length <= 1 && sources[0] == null)) {
source = new Source(module.id, sourcesContent[0]);
sourcemapChain = [module.originalSourcemap].concat(sourcemapChain);
sourcemapChain = [<RawSourceMap>originalSourcemap].concat(sourcemapChain);
} else {
// TODO indiscriminately treating IDs and sources as normal paths is probably bad.
const directory = dirname(module.id) || '.';
const sourceRoot = module.originalSourcemap.sourceRoot || '.';
const sourceRoot = originalSourcemap.sourceRoot || '.';

const baseSources = sources.map((source, i) => {
return new Source(resolve(directory, sourceRoot, source), sourcesContent[i]);
});

source = <any>new Link(<any>module.originalSourcemap, baseSources);
source = <any>new Link(<any>originalSourcemap, baseSources);
}
}

@@ -1,4 +1,4 @@
import { RawSourceMap } from '../rollup/types';
import { ExistingRawSourceMap, RawSourceMap } from '../rollup/types';

export function getOriginalLocation(
sourcemapChain: RawSourceMap[],
@@ -7,7 +7,7 @@ export function getOriginalLocation(
const filteredSourcemapChain = sourcemapChain.filter(sourcemap => sourcemap.mappings);

while (filteredSourcemapChain.length > 0) {
const sourcemap = filteredSourcemapChain.pop();
const sourcemap = <ExistingRawSourceMap>filteredSourcemapChain.pop();
const line: any = sourcemap.mappings[location.line - 1];
let locationFound = false;

0 comments on commit ad41348

Please sign in to comment.