Skip to content

Commit

Permalink
Use dependencies instead of bundle ids for markers and make the packa…
Browse files Browse the repository at this point in the history
…gers do replacement
  • Loading branch information
Will Binns-Smith committed Nov 4, 2019
1 parent 9b123ed commit 2a8a310
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 175 deletions.
2 changes: 1 addition & 1 deletion packages/bundlers/default/src/DefaultBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default new Bundler({

if (
dependency.isEntry ||
dependency.isAsync ||
(resolution && dependency.isAsync) ||
resolution?.isIsolated ||
resolution?.isInline
) {
Expand Down
74 changes: 1 addition & 73 deletions packages/core/core/src/PackagerRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,7 @@ export default class PackagerRunner {
});

return {
contents:
typeof packaged.contents === 'string'
? replaceReferences(
packaged.contents,
generateDepToBundlePath(internalBundle, bundleGraph)
)
: packaged.contents,
contents: packaged.contents,
map: packaged.map
};
}
Expand Down Expand Up @@ -477,72 +471,6 @@ function writeFileStream(
});
}

/*
* Build a mapping from async, url dependency ids to web-friendly relative paths
* to their bundles. These will be relative to the current bundle if `publicUrl`
* is not provided. If `publicUrl` is provided, the paths will be joined to it.
*
* These are used to translate any placeholder dependency ids written during
* transformation back to a path that can be loaded in a browser (such as
* in a "raw" loader or any transformed dependencies referred to by url).
*/
function generateDepToBundlePath(
bundle: InternalBundle,
bundleGraph: InternalBundleGraph
): Map<string, FilePath> {
let depToBundlePath: Map<string, FilePath> = new Map();
bundleGraph.traverseBundle(bundle, node => {
if (node.type !== 'dependency') {
return;
}

let dep = node.value;
if (!dep.isURL || !dep.isAsync) {
return;
}

let [bundleGroupNode] = bundleGraph._graph.getNodesConnectedFrom(node);
invariant(bundleGroupNode && bundleGroupNode.type === 'bundle_group');

let [entryBundleNode] = bundleGraph._graph.getNodesConnectedFrom(
bundleGroupNode,
'bundle'
);
invariant(entryBundleNode && entryBundleNode.type === 'bundle');

let entryBundle = entryBundleNode.value;
depToBundlePath.set(
dep.id,
urlJoin(
nullthrows(entryBundle.target).publicUrl ?? '/',
nullthrows(entryBundle.name)
)
);
});

return depToBundlePath;
}

// replace references to url dependencies with relative paths to their
// corresponding bundles.
// TODO: This likely alters the length of the column in the source text.
// Update any sourcemaps accordingly.
function replaceReferences(
code: string,
depToBundlePath: Map<string, FilePath>
): string {
let output = code;
for (let [depId, replacement] of depToBundlePath) {
let split = output.split(depId);
if (split.length > 1) {
// the dependency id was found in the text. replace it.
output = split.join(replacement);
}
}

return output;
}

function getContentKey(cacheKey: string) {
return md5FromString(`${cacheKey}:content`);
}
Expand Down
35 changes: 34 additions & 1 deletion packages/core/core/src/ResolverRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,41 @@ export default class ResolverRunner {

let resolvers = await this.config.getResolvers();

let pipeline;
let filePath;
if (dependency.moduleSpecifier.includes(':')) {
[pipeline, filePath] = dependency.moduleSpecifier.split(':');
let transformsWithPipelines = {};
for (let key of Object.keys(this.config.transforms)) {
if (key.includes(':')) {
transformsWithPipelines[key] = this.config.transforms[key];
}
}

if (
!(
this.config.matchGlobMapPipelines(
filePath,
transformsWithPipelines,
pipeline
)?.length > 0
)
) {
if (dep.isURL) {
// This may be a url protocol or scheme rather than a pipeline, such as
// `url('http://example.com/foo.png')`
return null;
} else {
throw new Error(`Unknown pipeline ${pipeline}.`);
}
}
} else {
filePath = dependency.moduleSpecifier;
}

for (let resolver of resolvers) {
let result = await resolver.resolve({
filePath,
dependency: dep,
options: this.pluginOptions
});
Expand All @@ -49,7 +82,7 @@ export default class ResolverRunner {
sideEffects: result.sideEffects,
code: result.code,
env: dependency.env,
pipeline: result.pipeline ?? dependency.pipeline
pipeline: pipeline ?? dependency.pipeline
};
}
}
Expand Down
15 changes: 2 additions & 13 deletions packages/core/core/src/public/Asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,22 +230,11 @@ export class MutableAsset extends BaseAsset implements IMutableAsset {
}

addURLDependency(url: string, opts: $Shape<DependencyOptions>): string {
if (isURL(url)) {
return url;
}

let parsed = URL.parse(url);
let pathname = parsed.pathname;
if (pathname == null) {
return url;
}

parsed.pathname = this.addDependency({
moduleSpecifier: decodeURIComponent(pathname),
return this.addDependency({
moduleSpecifier: url,
isURL: true,
isAsync: true, // The browser has native loaders for url dependencies
...opts
});
return URL.format(parsed);
}
}
21 changes: 21 additions & 0 deletions packages/core/integration-tests/test/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,25 @@ describe('css', () => {
// TODO: Make this `2` when a `sourceMappingURL` is added
assert.equal(css.split('\n').length, 1);
});

it('should inline data-urls for text-encoded files', async () => {
await bundle(path.join(__dirname, '/integration/data-url/text.css'));
let css = await outputFS.readFile(path.join(distDir, 'text.css'), 'utf8');
assert.equal(
css,
`.svg-img {
background-image: url('data:image/svg+xml,%3Csvg%3E%3C%2Fsvg%3E%0A');
}
`
);
});

it('should inline data-urls for binary files', async () => {
await bundle(path.join(__dirname, '/integration/data-url/binary.css'));
let css = await outputFS.readFile(path.join(distDir, 'binary.css'), 'utf8');
assert(
css.startsWith(`.webp-img {
background-image: url('data:image/webp;base64,UklGR`)
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.webp-img {
background-image: url('data-url:parcel.webp')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.svg-img {
background-image: url('data-url:img.svg');
}
6 changes: 3 additions & 3 deletions packages/core/types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,7 @@ export type ResolveResult = {|
filePath?: FilePath,
isExcluded?: boolean,
sideEffects?: boolean,
code?: string,
pipeline?: ?string
code?: string
|};

export type Bundler = {|
Expand Down Expand Up @@ -669,7 +668,8 @@ export type Optimizer = {|
export type Resolver = {|
resolve({|
dependency: Dependency,
options: PluginOptions
options: PluginOptions,
filePath: FilePath
|}): Async<?ResolveResult>
|};

Expand Down
1 change: 1 addition & 0 deletions packages/core/utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export {default as prettyDiagnostic} from './prettyDiagnostic';
export {default as PromiseQueue} from './PromiseQueue';
// $FlowFixMe this is untyped
export {default as promisify} from './promisify';
export {default as replaceBundleReferences} from './replaceBundleReferences';
export {default as syncPromise} from './syncPromise';
export {default as TapStream} from './TapStream';
export {default as urlJoin} from './urlJoin';
Expand Down
88 changes: 88 additions & 0 deletions packages/core/utils/src/replaceBundleReferences.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// @flow strict-local

import type SourceMap from '@parcel/source-map';
import type {Async, Blob, Bundle, BundleGraph} from '@parcel/types';

import {Readable} from 'stream';
import {bufferStream, urlJoin} from '../';

import nullthrows from 'nullthrows';

/*
* Replaces references to dependency ids with either:
* - in the case of an inline bundle, the packaged contents of that bundle
* - in the case of another bundle reference, the bundle's url from the publicUrl root
* - in the case of a url dependency that Parcel did not handle,
* the original moduleSpecifier. These are external requests.
*/
export default async function replaceBundleReferences({
bundle,
bundleGraph,
contents,
map,
formatInline = str => str,
getInlineBundleContents
}: {|
bundle: Bundle,
bundleGraph: BundleGraph,
contents: string,
formatInline: string => string,
getInlineBundleContents: (
Bundle,
BundleGraph
) => Async<{|contents: Blob, map: ?(Readable | string)|}>,
map?: ?SourceMap
|}) {
let replacements = new Map();

for (let {
dependency,
bundleGroup
} of bundleGraph.getBundleGroupsReferencedByBundle(bundle)) {
let [entryBundle] = bundleGraph.getBundlesInBundleGroup(bundleGroup);
if (entryBundle.isInline) {
// inline bundles
let packagedBundle = await getInlineBundleContents(
entryBundle,
bundleGraph
);
let packagedContents = (packagedBundle.contents instanceof Readable
? await bufferStream(packagedBundle.contents)
: packagedBundle.contents
).toString();
replacements.set(dependency.id, formatInline(packagedContents));
} else {
// url references
replacements.set(
dependency.id,
urlJoin(
entryBundle.target.publicUrl ?? '/',
nullthrows(entryBundle.name)
)
);
}
}

// external url references
bundle.traverse(node => {
if (node.type !== 'dependency') {
return;
}

let dependency = node.value;
if (dependency.isURL && !replacements.has(dependency.id)) {
replacements.set(dependency.id, dependency.moduleSpecifier);
}
});

let finalContents = contents;
for (let [depId, replacement] of replacements) {
finalContents = finalContents.replace(new RegExp(depId, 'g'), replacement);
}

return {
contents: finalContents,
// TODO: Update sourcemap with adjusted contents
map
};
}
11 changes: 8 additions & 3 deletions packages/packagers/css/src/CSSPackager.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// @flow

import {Packager} from '@parcel/plugin';
import {PromiseQueue} from '@parcel/utils';
import {PromiseQueue, replaceBundleReferences} from '@parcel/utils';

export default new Packager({
async package({bundle, bundleGraph}) {
async package({bundle, bundleGraph, getInlineBundleContents}) {
let queue = new PromiseQueue({maxConcurrent: 32});
bundle.traverseAssets({
exit: asset => {
Expand Down Expand Up @@ -33,6 +33,11 @@ export default new Packager({
});

let outputs = await queue.run();
return {contents: await outputs.map(output => output).join('\n')};
return replaceBundleReferences({
bundle,
bundleGraph,
contents: await outputs.map(output => output).join('\n'),
getInlineBundleContents
});
}
});
Loading

0 comments on commit 2a8a310

Please sign in to comment.