Skip to content

Commit

Permalink
Refactor to use packager to handle runtime code
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeLane committed Jul 30, 2024
1 parent aa394b2 commit 8225b85
Show file tree
Hide file tree
Showing 19 changed files with 133 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ pub(crate) fn convert_priority(
DependencyKind::Export => Priority::Sync,
DependencyKind::Require => Priority::Sync,
DependencyKind::File => Priority::Sync,
DependencyKind::DeferredForDisplayTierImport => Priority::Tier,
DependencyKind::DeferredTierImport => Priority::Tier,
DependencyKind::DeferredForDisplayImport => Priority::Tier,
DependencyKind::DeferredImport => Priority::Tier,
}
}

Expand All @@ -35,8 +35,8 @@ pub(crate) fn convert_specifier_type(
DependencyKind::Worklet => SpecifierType::Url,
DependencyKind::Url => SpecifierType::Url,
DependencyKind::File => SpecifierType::Custom,
DependencyKind::DeferredForDisplayTierImport => SpecifierType::Esm,
DependencyKind::DeferredTierImport => SpecifierType::Esm,
DependencyKind::DeferredForDisplayImport => SpecifierType::Esm,
DependencyKind::DeferredImport => SpecifierType::Esm,
}
}

Expand Down
14 changes: 8 additions & 6 deletions packages/bundlers/default/src/DefaultBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,7 @@ function decorateLegacyGraph(
);
for (let incomingDep of incomingDeps) {
if (
(incomingDep.priority === 'lazy' ||
incomingDep.priority === 'tier') &&
incomingDep.priority === 'lazy' &&
incomingDep.specifierType !== 'url' &&
bundle.hasDependency(incomingDep)
) {
Expand All @@ -296,7 +295,7 @@ function decorateLegacyGraph(
let incomingDeps = bundleGraph.getIncomingDependencies(manualSharedAsset);
for (let incomingDep of incomingDeps) {
if (
(incomingDep.priority === 'lazy' || incomingDep.priority === 'tier') &&
incomingDep.priority === 'lazy' &&
incomingDep.specifierType !== 'url'
) {
let bundles = bundleGraph.getBundlesWithDependency(incomingDep);
Expand Down Expand Up @@ -497,7 +496,7 @@ function createIdealGraph(

if (
node.type === 'dependency' &&
(node.value.priority === 'lazy' || node.value.priority === 'tier') &&
node.value.priority === 'lazy' &&
parentAsset
) {
// Don't walk past the bundle group assets
Expand Down Expand Up @@ -586,7 +585,6 @@ function createIdealGraph(
}
if (
dependency.priority === 'lazy' ||
dependency.priority === 'tier' ||
childAsset.bundleBehavior === 'isolated' // An isolated Dependency, or Bundle must contain all assets it needs to load.
) {
if (bundleId == null) {
Expand Down Expand Up @@ -848,6 +846,7 @@ function createIdealGraph(

if (
dependency.priority !== 'sync' &&
dependency.priority !== 'tier' &&
dependencyBundleGraph.hasContentKey(dependency.id)
) {
let assets = assetGraph.getDependencyAssets(dependency);
Expand Down Expand Up @@ -875,7 +874,10 @@ function createIdealGraph(
}
}

if (dependency.priority !== 'sync') {
if (
dependency.priority !== 'sync' &&
dependency.priority !== 'tier'
) {
actions.skipChildren();
}
return;
Expand Down
4 changes: 3 additions & 1 deletion packages/core/feature-flags/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export type FeatureFlags = {|
*/
+parcelV3: boolean,
/**
* Tiered imports API
* Enable tier imports
*
* Tier imports allow developers to have control over when code is loaded
*/
+tieredImports: boolean,
|};
5 changes: 2 additions & 3 deletions packages/examples/phases/phases.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
type ModuleRef<_> = string;
type ErrorMessage = 'You must annotate type with "<typeof import(\'xyz\')>"';

interface DeferredImport<T> {
onReady(resource: () => void): () => void;
mod: T | null;
interface DeferredImport<T extends {default: any}> {
onReady(resource: (mod: T['default']) => void): void;
}

declare function importDeferredForDisplay<T extends any | void = void>(
Expand Down
31 changes: 18 additions & 13 deletions packages/examples/phases/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import React, {FC, Suspense, useEffect} from 'react';
import React, {Suspense} from 'react';
import ReactDOM from 'react-dom';

import ModulePhase1 from './phase1';
import Tier1 from './tier1';
const DeferredTier2 =
importDeferredForDisplay<typeof import('./tier2')>('./tier2');
const DeferredTier3 = importDeferred<typeof import('./tier3')>('./tier3');

import {deferredLoadComponent} from './utils';
const Phase2 = deferredLoadComponent(
importDeferredForDisplay<typeof import('./phase2')>('./phase2'),
);
const Phase3 = deferredLoadComponent(
importDeferred<typeof import('./phase3')>('./phase3'),
);

const Tier2 = deferredLoadComponent(DeferredTier2);
const Tier3Instance1 = deferredLoadComponent(DeferredTier3);
const Tier3Instance2 = deferredLoadComponent(DeferredTier3);

function App() {
return (
<>
<div>App</div>
<ModulePhase1 />
<Suspense fallback={<div>Loading...</div>}>
<Phase2 />
<Tier1 />
<Suspense fallback={<div>Loading Tier 2...</div>}>
<Tier2 />
</Suspense>
<Suspense fallback={<div>Loading Tier 3 instance 1...</div>}>
<Tier3Instance1 />
</Suspense>
<Suspense fallback={<div>Loading...</div>}>
<Phase3 />
<Suspense fallback={<div>Loading Tier 3 instance 2...</div>}>
<Tier3Instance2 />
</Suspense>
</>
);
Expand Down
6 changes: 3 additions & 3 deletions packages/examples/phases/src/lazy.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';

const Lazy = () => <div>Lazy</div>;

export default Lazy;
export default function Lazy() {
return <div>Lazy</div>;
}
5 changes: 0 additions & 5 deletions packages/examples/phases/src/phase1.tsx

This file was deleted.

5 changes: 0 additions & 5 deletions packages/examples/phases/src/phase2.tsx

This file was deleted.

5 changes: 0 additions & 5 deletions packages/examples/phases/src/phase3.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions packages/examples/phases/src/tier1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default function Tier1() {
return <div>Tier 1</div>;
}
5 changes: 5 additions & 0 deletions packages/examples/phases/src/tier2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default function Tier2() {
return <div>Tier 2</div>;
}
5 changes: 5 additions & 0 deletions packages/examples/phases/src/tier3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default function Tier3() {
return <div>Tier 3</div>;
}
40 changes: 23 additions & 17 deletions packages/examples/phases/src/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import React, {FC, useEffect} from 'react';
import React, {FC} from 'react';

let loaderMap = new WeakMap<DeferredImport<any>, Promise<any>>();
let componentMap = new WeakMap<DeferredImport<any>, any>();

export function deferredLoadComponent<T>(
Resource: DeferredImport<T extends {default: any} ? T : never>,
resource: DeferredImport<T extends {default: any} ? T : never>,
): FC {
let loaded = false;
let cleanUp: undefined | (() => void);
if (!loaderMap.has(resource)) {
loaderMap.set(
resource,
new Promise(resolve => {
resource.onReady(component => {
componentMap.set(resource, component);
resolve(component);
});
}),
);
}

return function WrappedComponent(props) {
useEffect(() => {
return () => {
cleanUp?.();
};
}, []);
if (loaded) {
return <Resource.mod.default {...props} />;
const Component = componentMap.get(resource);
if (Component) {
return <Component {...props} />;
} else {
throw new Promise(resolve => {
cleanUp = Resource.onReady(() => {
loaded = true;
resolve(Resource);
});
});
throw (
loaderMap.get(resource) ?? new Error(`Loader map did not have resource`)
);
}
};
}
29 changes: 18 additions & 11 deletions packages/packagers/js/src/ScopeHoistingPackager.js
Original file line number Diff line number Diff line change
Expand Up @@ -741,22 +741,29 @@ ${code}
}

let symbol = this.getSymbolResolution(asset, resolved, imported, dep);
replacements.set(
local,
// If this was an internalized async asset, wrap in a Promise.resolve.
asyncResolution?.type === 'asset'
? `Promise.resolve(${symbol})`
: symbol,
);

if (
this.options.featureFlags.tieredImports &&
dep.priority === 'tier'
) {
// Wrap tiered import symbols with tier helper
replacements.set(local, `$parcel$tier(${symbol})`);
this.usedHelpers.add('$parcel$tier');
} else {
replacements.set(
local,
// If this was an internalized async asset, wrap in a Promise.resolve.
asyncResolution?.type === 'asset'
? `Promise.resolve(${symbol})`
: symbol,
);
}
}

// Async dependencies need a namespace object even if all used symbols were statically analyzed.
// This is recorded in the promiseSymbol meta property set by the transformer rather than in
// symbols so that we don't mark all symbols as used.
if (
(dep.priority === 'lazy' || dep.priority === 'tier') &&
dep.meta.promiseSymbol
) {
if (dep.priority === 'lazy' && dep.meta.promiseSymbol) {
let promiseSymbol = dep.meta.promiseSymbol;
invariant(typeof promiseSymbol === 'string');
let symbol = this.getSymbolResolution(asset, resolved, '*', dep);
Expand Down
27 changes: 27 additions & 0 deletions packages/packagers/js/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,37 @@ function $parcel$defineInteropFlag(a) {
}
`;

const $parcel$tier = `
function $parcel$tier(loader) {
var listeners = new Set();
var resolved = false;
if (loader instanceof Promise) {
loader.then((mod) => {
resolved = true;
for (let listener of listeners) {
listener?.(mod.default);
}
});
} else {
resolved = true;
}
return {
onReady: (listener) => {
if (resolved) {
listener(loader.default);
} else {
listeners.add(listener);
}
},
};
}
`;

export const helpers = {
$parcel$export,
$parcel$exportWildcard,
$parcel$interopDefault,
$parcel$global,
$parcel$defineInteropFlag,
$parcel$tier,
};
15 changes: 1 addition & 14 deletions packages/runtimes/js/src/JSRuntime.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ function getDependencies(bundle: NamedBundle): {|

let dependency = node.value;
if (
(dependency.priority === 'lazy' || dependency.priority === 'tier') &&
dependency.priority === 'lazy' &&
dependency.specifierType !== 'url'
) {
asyncDependencies.push(dependency);
Expand Down Expand Up @@ -475,33 +475,20 @@ function getLoaderRuntime({
loaderCode = `(${loaderCode})`;
}

let needsTierPrelude = false;
if (mainBundle.type === 'js') {
let parcelRequire = bundle.env.shouldScopeHoist
? 'parcelRequire'
: 'module.bundle.root';
loaderCode += `.then(() => ${parcelRequire}('${bundleGraph.getAssetPublicId(
bundleGraph.getAssetById(bundleGroup.entryAssetId),
)}'))`;

if (
options.featureFlags.tieredImports &&
(dependency.meta.kind === 'DeferredForDisplayTierImport' ||
dependency.meta.kind === 'DeferredTierImport')
) {
loaderCode = `tier(${loaderCode})`;
needsTierPrelude = true;
}
}

let code = [];

if (needsEsmLoadPrelude) {
code.push(`let load = require('./helpers/browser/esm-js-loader');`);
}
if (needsTierPrelude) {
code.push(`let tier = require('./helpers/browser/tier-loader');`);
}

code.push(`module.exports = ${loaderCode};`);

Expand Down
23 changes: 0 additions & 23 deletions packages/runtimes/js/src/helpers/browser/tier-loader.js

This file was deleted.

Loading

0 comments on commit 8225b85

Please sign in to comment.