diff --git a/packages/examples/phases/package.json b/packages/examples/phases/package.json
index 74f3056df8c..7fd42eec7ae 100644
--- a/packages/examples/phases/package.json
+++ b/packages/examples/phases/package.json
@@ -5,7 +5,7 @@
"private": true,
"scripts": {
"start": "parcel serve src/index.html --no-cache --https --feature-flag tieredImports=true",
- "start:prod": "yarn build && npx http-server dist/",
+ "start:prod": "yarn build && npx http-server -p 1234 dist/",
"build": "PARCEL_WORKERS=0 parcel build src/index.html --no-cache --feature-flag tieredImports=true",
"debug": "PARCEL_WORKERS=0 node --inspect-brk $(yarn bin parcel) serve src/index.html --no-cache --https --feature-flag tieredImports=true --no-hmr",
"debug:prod": "PARCEL_WORKERS=0 node --inspect-brk $(yarn bin parcel) build src/index.html --no-cache --feature-flag tieredImports=true"
diff --git a/packages/examples/phases/src/utils.tsx b/packages/examples/phases/src/utils.tsx
index 625f04b7a47..e125e8e7637 100644
--- a/packages/examples/phases/src/utils.tsx
+++ b/packages/examples/phases/src/utils.tsx
@@ -1,15 +1,21 @@
import React, {
ComponentType,
ForwardRefExoticComponent,
+ ForwardedRef,
+ MemoExoticComponent,
+ PropsWithChildren,
PropsWithoutRef,
RefAttributes,
forwardRef,
+ memo,
} from 'react';
export function deferredLoadComponent
(
resource: DeferredImport<{default: ComponentType
}>,
-): ForwardRefExoticComponent<
- PropsWithoutRef
& RefAttributes>
+): MemoExoticComponent<
+ ForwardRefExoticComponent<
+ PropsWithoutRef & RefAttributes>
+ >
> {
// Create a deferred component map in the global context, so we can reuse the components everywhere
if (!globalThis.deferredComponentMap) {
@@ -21,25 +27,31 @@ export function deferredLoadComponent(
}
let Component: ComponentType | undefined;
- const loader = new Promise(resolve => {
+ let loader = new Promise(resolve => {
resource.onReady(loaded => {
Component = loaded;
resolve(loaded);
});
});
- const wrapper = forwardRef, P>(function DeferredComponent(
- props,
- ref,
+ const wrapper = function DeferredComponent(
+ props: PropsWithChildren,
+ ref: ForwardedRef>,
) {
if (Component) {
return ;
- } else {
- throw loader;
}
- });
- // Store in weakmap so we only have one instance
- globalThis.deferredComponentMap.set(resource, wrapper);
- return wrapper;
+ throw loader;
+ };
+
+ // Support refs in the deferred component
+ const forwardedRef = forwardRef(wrapper);
+
+ // Memoise so we avoid re-renders
+ const memoised = memo(forwardedRef);
+
+ // Store in weak map so we only have one instance
+ globalThis.deferredComponentMap.set(resource, memoised);
+ return memoised;
}
diff --git a/packages/packagers/js/src/ScopeHoistingPackager.js b/packages/packagers/js/src/ScopeHoistingPackager.js
index b7f129b16f3..0b1bc0d6a93 100644
--- a/packages/packagers/js/src/ScopeHoistingPackager.js
+++ b/packages/packagers/js/src/ScopeHoistingPackager.js
@@ -741,13 +741,22 @@ ${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.
diff --git a/packages/packagers/js/src/dev-prelude.js b/packages/packagers/js/src/dev-prelude.js
index a44249cef3d..cc5b8a5f84c 100644
--- a/packages/packagers/js/src/dev-prelude.js
+++ b/packages/packagers/js/src/dev-prelude.js
@@ -109,6 +109,29 @@
{},
];
};
+ newRequire.tier = function (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);
+ }
+ },
+ };
+ };
Object.defineProperty(newRequire, 'root', {
get: function () {
diff --git a/packages/packagers/js/src/helpers.js b/packages/packagers/js/src/helpers.js
index de455c32c57..6144d9cbee3 100644
--- a/packages/packagers/js/src/helpers.js
+++ b/packages/packagers/js/src/helpers.js
@@ -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,
};
diff --git a/packages/transformers/js/core/src/dependency_collector.rs b/packages/transformers/js/core/src/dependency_collector.rs
index c28239c9204..3912be20bf2 100644
--- a/packages/transformers/js/core/src/dependency_collector.rs
+++ b/packages/transformers/js/core/src/dependency_collector.rs
@@ -5,7 +5,6 @@ use std::hash::Hash;
use std::hash::Hasher;
use std::path::Path;
use swc_core::ecma::ast::MemberExpr;
-use swc_core::ecma::ast::Module;
use swc_core::ecma::utils::stack_size::maybe_grow_default;
use path_slash::PathBufExt;
@@ -134,15 +133,14 @@ pub struct DependencyDescriptor {
/// This pass collects dependencies in a module and compiles references as needed to work with Parcel's JSRuntime.
pub fn dependency_collector<'a>(
- module: Module,
source_map: &'a SourceMap,
items: &'a mut Vec,
ignore_mark: swc_core::common::Mark,
unresolved_mark: swc_core::common::Mark,
config: &'a Config,
diagnostics: &'a mut Vec,
-) -> (Module, bool) {
- let mut fold = DependencyCollector {
+) -> impl Fold + 'a {
+ DependencyCollector {
source_map,
items,
in_try: false,
@@ -153,12 +151,7 @@ pub fn dependency_collector<'a>(
config,
diagnostics,
import_meta: None,
- needs_tier_helpers: false,
- };
-
- let module = module.fold_with(&mut fold);
-
- (module, fold.needs_tier_helpers)
+ }
}
struct DependencyCollector<'a> {
@@ -172,7 +165,6 @@ struct DependencyCollector<'a> {
config: &'a Config,
diagnostics: &'a mut Vec,
import_meta: Option,
- needs_tier_helpers: bool,
}
impl<'a> DependencyCollector<'a> {
@@ -340,26 +332,6 @@ impl<'a> Fold for DependencyCollector<'a> {
);
}
- if self.needs_tier_helpers {
- res.body.insert(
- 0,
- ast::ModuleItem::Stmt(ast::Stmt::Decl(ast::Decl::Var(Box::new(ast::VarDecl {
- span: DUMMY_SP,
- kind: ast::VarDeclKind::Var,
- decls: vec![ast::VarDeclarator {
- span: DUMMY_SP,
- name: ast::Pat::Ident(ast::Ident::new("parcel$tier$".into(), DUMMY_SP).into()),
- init: Some(Box::new(ast::Expr::Call(crate::utils::create_require(
- "@parcel/transformer-js/src/tier-helpers.js".into(),
- self.unresolved_mark,
- )))),
- definite: false,
- }],
- declare: false,
- })))),
- );
- }
-
res
}
@@ -830,9 +802,6 @@ impl<'a> Fold for DependencyCollector<'a> {
});
}
- // Track that we need to add the dependency to the asset
- self.needs_tier_helpers = true;
-
// Convert to require without scope hoisting
if !self.config.scope_hoist && !self.config.standalone {
let name = match &self.config.source_type {
@@ -849,19 +818,24 @@ impl<'a> Fold for DependencyCollector<'a> {
let rewritten_call = rewrite_require_specifier(call, self.unresolved_mark);
self.require_node = Some(rewritten_call.clone());
- // Wrap with the parcelTiers helper
- ast::CallExpr {
- span: DUMMY_SP,
- type_args: None,
- args: vec![rewritten_call.as_arg()],
- callee: ast::Callee::Expr(Box::new(Member(MemberExpr {
- obj: Box::new(ast::Expr::Ident(ast::Ident::new(
- "parcel$tier$".into(),
- DUMMY_SP,
- ))),
- prop: MemberProp::Ident(ast::Ident::new("load".into(), DUMMY_SP)),
+ if !self.config.scope_hoist && !self.config.standalone {
+ // Wrap with parcel tiers when not scope hoisting
+ ast::CallExpr {
span: DUMMY_SP,
- }))),
+ type_args: None,
+ args: vec![rewritten_call.as_arg()],
+ callee: ast::Callee::Expr(Box::new(Member(MemberExpr {
+ obj: Box::new(Member(MemberExpr {
+ obj: Box::new(ast::Expr::Ident(ast::Ident::new("module".into(), DUMMY_SP))),
+ prop: MemberProp::Ident(ast::Ident::new("bundle".into(), DUMMY_SP)),
+ span: DUMMY_SP,
+ })),
+ prop: MemberProp::Ident(ast::Ident::new("tier".into(), DUMMY_SP)),
+ span: DUMMY_SP,
+ }))),
+ }
+ } else {
+ rewritten_call
}
}
_ => node.fold_children_with(self),
diff --git a/packages/transformers/js/core/src/lib.rs b/packages/transformers/js/core/src/lib.rs
index f3d18f305fa..33e1395ed7a 100644
--- a/packages/transformers/js/core/src/lib.rs
+++ b/packages/transformers/js/core/src/lib.rs
@@ -145,7 +145,6 @@ pub struct TransformResult {
pub used_env: HashSet,
pub has_node_replacements: bool,
pub is_constant_module: bool,
- pub needs_tier_helpers: bool,
}
fn targets_to_versions(targets: &Option>) -> Option {
@@ -460,16 +459,17 @@ pub fn transform(
};
let ignore_mark = Mark::fresh(Mark::root());
- let (module, needs_tier_helpers) = dependency_collector(
- module,
- &source_map,
- &mut result.dependencies,
- ignore_mark,
- unresolved_mark,
- &config,
- &mut diagnostics,
+ let module = module.fold_with(
+ // Collect dependencies
+ &mut dependency_collector(
+ &source_map,
+ &mut result.dependencies,
+ ignore_mark,
+ unresolved_mark,
+ &config,
+ &mut diagnostics,
+ ),
);
- result.needs_tier_helpers = needs_tier_helpers;
diagnostics.extend(error_buffer_to_diagnostics(&error_buffer, &source_map));
diff --git a/packages/transformers/js/src/JSTransformer.js b/packages/transformers/js/src/JSTransformer.js
index ff9487721e2..611e25cb48c 100644
--- a/packages/transformers/js/src/JSTransformer.js
+++ b/packages/transformers/js/src/JSTransformer.js
@@ -415,7 +415,6 @@ export default (new Transformer({
hoist_result,
symbol_result,
needs_esm_helpers,
- needs_tier_helpers,
diagnostics,
used_env,
has_node_replacements,
@@ -1071,19 +1070,6 @@ export default (new Transformer({
}
}
- if (needs_tier_helpers) {
- asset.addDependency({
- specifier: '@parcel/transformer-js/src/tier-helpers.js',
- specifierType: 'esm',
- resolveFrom: __filename,
- env: {
- includeNodeModules: {
- '@parcel/transformer-js': true,
- },
- },
- });
- }
-
asset.type = 'js';
asset.setBuffer(compiledCode);
diff --git a/packages/transformers/js/src/tier-helpers.js b/packages/transformers/js/src/tier-helpers.js
deleted file mode 100644
index b9e1a944575..00000000000
--- a/packages/transformers/js/src/tier-helpers.js
+++ /dev/null
@@ -1,23 +0,0 @@
-export function load(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);
- }
- },
- };
-}