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

Parcel 2: Serializer for complex object types #2522

Merged
merged 10 commits into from
Jan 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/bundlers/default/src/DefaultBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import type {Dependency, BundleGroup, Bundle} from '@parcel/types';
import {Bundler} from '@parcel/plugin';

const ISOLATED_ENVS = new Set(['web-worker', 'service-worker']);
const OPTIONS = {
minBundles: 1,
minBundleSize: 30000,
Expand Down Expand Up @@ -32,7 +31,7 @@ export default new Bundler({

// Start a new bundle if this is an async dependency, or entry point.
if (dep.isAsync || dep.isEntry) {
let isIsolated = dep.isEntry || ISOLATED_ENVS.has(dep.env.context);
let isIsolated = dep.isEntry || dep.env.isIsolated();
let bundleGroup: BundleGroup = {
dependency: dep,
target: dep.target || (context && context.bundleGroup.target)
Expand All @@ -54,7 +53,7 @@ export default new Bundler({
let dep = context.bundleGroup.dependency;

// Mark bundle as an entry, and set explicit file path from target if the dependency has one
bundle.isEntry = dep.isEntry;
bundle.isEntry = !!dep.isEntry;
if (dep.target && dep.target.distPath) {
bundle.filePath = dep.target.distPath;
}
Expand Down
33 changes: 25 additions & 8 deletions packages/core/cache/src/Cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
Asset,
Environment
} from '@parcel/types';
import {serialize, deserialize} from '@parcel/utils/serializer';

// These keys can affect the output, so if they differ, the cache should not match
// const OPTION_KEYS = ['publicURL', 'minify', 'hmr', 'target', 'scopeHoist'];
Expand All @@ -24,12 +25,12 @@ const DEFAULT_CACHE_DIR = '.parcel-cache';
// Cache for whether a cache dir exists
const existsCache = new Set();

export default class Cache {
export class Cache {
dir: FilePath;
invalidated: Set<FilePath>;
optionsHash: string;

constructor(options: CLIOptions) {
init(options: CLIOptions) {
this.dir = Path.resolve(options.cacheDir || DEFAULT_CACHE_DIR);
this.invalidated = new Set();
this.optionsHash = objectHash(
Expand All @@ -39,7 +40,7 @@ export default class Cache {
);
}

static async createCacheDir(dir: FilePath = DEFAULT_CACHE_DIR) {
async createCacheDir(dir: FilePath = DEFAULT_CACHE_DIR) {
dir = Path.resolve(dir);
if (existsCache.has(dir)) {
return;
Expand Down Expand Up @@ -72,15 +73,15 @@ export default class Cache {
if (Buffer.isBuffer(data)) {
blobPath += '.bin';
} else {
data = JSON.stringify(data);
data = serialize(data);
if (type !== 'json') {
blobPath += '.json';
}
}
}

await fs.writeFile(blobPath, data);
return Path.relative(this.dir, blobPath);
return new CacheReference(Path.relative(this.dir, blobPath));
}

async _writeBlobs(assets: Array<Asset>) {
Expand All @@ -94,6 +95,7 @@ export default class Cache {
asset.output[blobKey]
);
}

return asset;
})
);
Expand Down Expand Up @@ -128,7 +130,7 @@ export default class Cache {
});

if (extension === '.json') {
data = JSON.parse(data);
data = deserialize(data);
}

return data;
Expand All @@ -137,8 +139,10 @@ export default class Cache {
async readBlobs(asset: Asset) {
await Promise.all(
Object.keys(asset.output).map(async blobKey => {
if (typeof asset.output[blobKey] === 'string') {
asset.output[blobKey] = await this.readBlob(asset.output[blobKey]);
if (asset.output[blobKey] instanceof CacheReference) {
asset.output[blobKey] = await this.readBlob(
asset.output[blobKey].filePath
);
}
})
);
Expand Down Expand Up @@ -172,3 +176,16 @@ export default class Cache {
}
}
}

export class CacheReference {
filePath: FilePath;
constructor(filePath: FilePath) {
this.filePath = filePath;
}

static deserialize(value: {filePath: FilePath}) {
return new CacheReference(value.filePath);
}
}

export default new Cache();
43 changes: 15 additions & 28 deletions packages/core/core/src/Asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
Asset as IAsset,
TransformerResult,
DependencyOptions,
Dependency,
Dependency as IDependency,
FilePath,
File,
Environment,
Expand All @@ -13,10 +13,10 @@ import type {
Config,
PackageJSON
} from '@parcel/types';
import type Cache from '@parcel/cache';
import Cache from '@parcel/cache';
import md5 from '@parcel/utils/md5';
import {loadConfig} from '@parcel/utils/config';
import createDependency from './createDependency';
import Dependency from './Dependency';

type AssetOptions = {
id?: string,
Expand All @@ -25,13 +25,12 @@ type AssetOptions = {
type: string,
code?: string,
ast?: ?AST,
dependencies?: Array<Dependency>,
dependencies?: Array<IDependency>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there ever a time where one of the dependencies would not be an instance of the Dependency class? Do we expect something other than the Dependency class to implement the interface? Just wondering if the interface is necessary. Not a blocker though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... I don't think so, but flow was complaining since the types in @parcel/types refer to the interface not the class. Is there a better way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right. Not sure of another way besides pulling those classes out of core, which we didn't want to do. So yeah, this seems like the best option.

connectedFiles?: Array<File>,
output?: AssetOutput,
outputHash?: string,
env: Environment,
meta?: JSONObject,
cache?: Cache
meta?: JSONObject
};

export default class Asset implements IAsset {
Expand All @@ -41,14 +40,13 @@ export default class Asset implements IAsset {
type: string;
code: string;
ast: ?AST;
dependencies: Array<Dependency>;
dependencies: Array<IDependency>;
connectedFiles: Array<File>;
output: AssetOutput;
outputSize: number;
outputHash: string;
env: Environment;
meta: JSONObject;
#cache; // no type annotation because prettier dies...

constructor(options: AssetOptions) {
this.id =
Expand All @@ -70,10 +68,9 @@ export default class Asset implements IAsset {
this.outputHash = options.outputHash || '';
this.env = options.env;
this.meta = options.meta || {};
this.#cache = options.cache;
}

toJSON(): AssetOptions {
serialize(): AssetOptions {
// Exclude `code` and `ast` from cache
return {
id: this.id,
Expand All @@ -91,14 +88,12 @@ export default class Asset implements IAsset {
}

addDependency(opts: DependencyOptions) {
let dep = createDependency(
{
...opts,
env: mergeEnvironment(this.env, opts.env)
},
this.filePath
);

// $FlowFixMe
let dep = new Dependency({
...opts,
env: this.env.merge(opts.env),
sourcePath: this.filePath
});
this.dependencies.push(dep);
return dep.id;
}
Expand All @@ -119,7 +114,7 @@ export default class Asset implements IAsset {
type: result.type,
code,
ast: result.ast,
env: mergeEnvironment(this.env, result.env),
env: this.env.merge(result.env),
dependencies: this.dependencies,
connectedFiles: this.connectedFiles,
output: result.output,
Expand All @@ -144,11 +139,7 @@ export default class Asset implements IAsset {
}

async getOutput() {
if (this.#cache) {
await this.#cache.readBlobs(this);
this.#cache = null;
}

await Cache.readBlobs(this);
return this.output;
}

Expand Down Expand Up @@ -179,7 +170,3 @@ export default class Asset implements IAsset {
return await this.getConfig(['package.json']);
}
}

function mergeEnvironment(a: Environment, b: ?Environment): Environment {
return Object.assign({}, a, b);
}
14 changes: 7 additions & 7 deletions packages/core/core/src/AssetGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Graph, {Node, type NodeId} from './Graph';
import type {
CacheEntry,
Dependency,
Dependency as IDependency,
Asset,
File,
FilePath,
Expand All @@ -15,15 +15,15 @@ import type {
DependencyResolution
} from '@parcel/types';
import md5 from '@parcel/utils/md5';
import createDependency from './createDependency';
import Dependency from './Dependency';

export const nodeFromRootDir = (rootDir: string) => ({
id: rootDir,
type: 'root',
value: rootDir
});

export const nodeFromDep = (dep: Dependency) => ({
export const nodeFromDep = (dep: IDependency) => ({
id: dep.id,
type: 'dependency',
value: dep
Expand Down Expand Up @@ -106,7 +106,7 @@ export default class AssetGraph extends Graph {
for (let entry of entries) {
for (let target of targets) {
let node = nodeFromDep(
createDependency({
new Dependency({
moduleSpecifier: entry,
target: target,
env: target.env,
Expand All @@ -133,7 +133,7 @@ export default class AssetGraph extends Graph {
* Marks a dependency as resolved, and connects it to a transformer
* request node for the file it was resolved to.
*/
resolveDependency(dep: Dependency, req: TransformerRequest): DepUpdates {
resolveDependency(dep: IDependency, req: TransformerRequest): DepUpdates {
let newRequest;

let depNode = nodeFromDep(dep);
Expand Down Expand Up @@ -221,7 +221,7 @@ export default class AssetGraph extends Graph {
}
}

getDependencies(asset: Asset): Array<Dependency> {
getDependencies(asset: Asset): Array<IDependency> {
let node = this.getNode(asset.id);
if (!node) {
return [];
Expand All @@ -230,7 +230,7 @@ export default class AssetGraph extends Graph {
return this.getNodesConnectedFrom(node).map(node => node.value);
}

getDependencyResolution(dep: Dependency): DependencyResolution {
getDependencyResolution(dep: IDependency): DependencyResolution {
let depNode = this.getNode(dep.id);
if (!depNode) {
return {};
Expand Down
52 changes: 52 additions & 0 deletions packages/core/core/src/Dependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @flow
import type {
DependencyOptions,
Dependency as IDependency,
Environment as IEnvironment,
SourceLocation,
Meta,
Target,
ModuleSpecifier,
FilePath
} from '@parcel/types';
import md5 from '@parcel/utils/md5';

type DependencyOpts = {
...DependencyOptions,
moduleSpecifier: ModuleSpecifier,
env: IEnvironment,
id?: string,
sourcePath?: FilePath
};

export default class Dependency implements IDependency {
id: string;
moduleSpecifier: ModuleSpecifier;
isAsync: ?boolean;
isEntry: ?boolean;
isOptional: ?boolean;
isURL: ?boolean;
loc: ?SourceLocation;
env: IEnvironment;
meta: ?Meta;
target: ?Target;
sourcePath: FilePath;

constructor(opts: DependencyOpts) {
this.moduleSpecifier = opts.moduleSpecifier;
this.isAsync = opts.isAsync;
this.isEntry = opts.isEntry;
this.isOptional = opts.isOptional;
this.isURL = opts.isURL;
this.loc = opts.loc;
this.meta = opts.meta;
this.target = opts.target;
this.env = opts.env;
this.sourcePath = opts.sourcePath || ''; // TODO: get from graph?
this.id =
opts.id ||
md5(
`${this.sourcePath}:${this.moduleSpecifier}:${JSON.stringify(this.env)}`
);
}
}
Loading