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); - } - }, - }; -}