or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-management.mdapplication-status-lifecycle.mdconfiguration-timeouts.mderror-handling.mdindex.mdnavigation-routing.mdparcels-system.md

parcels-system.mddocs/

0

# Parcels System

1

2

Manual component management system for mounting components outside of the standard application lifecycle. Parcels provide a way to manually control when and where components are mounted.

3

4

## Capabilities

5

6

### Mount Root Parcel

7

8

Mounts a parcel at the root level, not within a specific application. This is useful for shared components like modals, tooltips, or notifications that need to be managed independently.

9

10

```javascript { .api }

11

/**

12

* Mount a parcel at the root level

13

* @param parcelConfig - Parcel configuration object or loading function

14

* @param parcelProps - Properties including DOM element and custom props

15

* @returns Parcel instance with lifecycle methods

16

*/

17

function mountRootParcel(

18

parcelConfig: ParcelConfig,

19

parcelProps: ParcelProps & CustomProps

20

): Parcel;

21

22

type ParcelConfig = ParcelConfigObject | (() => Promise<ParcelConfigObject>);

23

24

interface ParcelConfigObject {

25

name?: string;

26

bootstrap: LifeCycleFn | Array<LifeCycleFn>;

27

mount: LifeCycleFn | Array<LifeCycleFn>;

28

unmount: LifeCycleFn | Array<LifeCycleFn>;

29

update?: LifeCycleFn | Array<LifeCycleFn>;

30

}

31

32

interface ParcelProps {

33

domElement: HTMLElement;

34

}

35

36

interface Parcel {

37

mount(): Promise<null>;

38

unmount(): Promise<null>;

39

update?(customProps: CustomProps): Promise<any>;

40

getStatus(): ParcelStatus;

41

loadPromise: Promise<null>;

42

bootstrapPromise: Promise<null>;

43

mountPromise: Promise<null>;

44

unmountPromise: Promise<null>;

45

}

46

47

type ParcelStatus =

48

| "NOT_LOADED"

49

| "LOADING_SOURCE_CODE"

50

| "NOT_BOOTSTRAPPED"

51

| "BOOTSTRAPPING"

52

| "NOT_MOUNTED"

53

| "MOUNTING"

54

| "MOUNTED"

55

| "UNMOUNTING"

56

| "UNLOADING"

57

| "SKIP_BECAUSE_BROKEN"

58

| "LOAD_ERROR";

59

```

60

61

**Usage Examples:**

62

63

```javascript

64

import { mountRootParcel } from "single-spa";

65

66

// Mount a simple parcel

67

const modalContainer = document.getElementById("modal-container");

68

const modalParcel = mountRootParcel(

69

() => import("./modal/modal.parcel.js"),

70

{

71

domElement: modalContainer,

72

title: "User Settings",

73

onClose: () => modalParcel.unmount()

74

}

75

);

76

77

// Mount parcel with configuration object

78

const tooltipParcel = mountRootParcel(

79

{

80

name: "tooltip",

81

bootstrap: () => Promise.resolve(),

82

mount: (props) => {

83

props.domElement.innerHTML = `<div class="tooltip">${props.text}</div>`;

84

return Promise.resolve();

85

},

86

unmount: (props) => {

87

props.domElement.innerHTML = "";

88

return Promise.resolve();

89

}

90

},

91

{

92

domElement: document.getElementById("tooltip"),

93

text: "This is a tooltip"

94

}

95

);

96

97

// Mount parcel with lifecycle management

98

async function createNotificationParcel(message, type = "info") {

99

const container = document.createElement("div");

100

document.body.appendChild(container);

101

102

const parcel = mountRootParcel(

103

() => import("./notification/notification.parcel.js"),

104

{

105

domElement: container,

106

message,

107

type,

108

onDismiss: async () => {

109

await parcel.unmount();

110

document.body.removeChild(container);

111

}

112

}

113

);

114

115

// Auto-dismiss after 5 seconds

116

setTimeout(async () => {

117

if (parcel.getStatus() === "MOUNTED") {

118

await parcel.unmount();

119

document.body.removeChild(container);

120

}

121

}, 5000);

122

123

return parcel;

124

}

125

```

126

127

### Parcel Lifecycle Management

128

129

Parcels follow a similar lifecycle to applications but are manually controlled:

130

131

```javascript

132

import { mountRootParcel } from "single-spa";

133

134

async function manageParcels() {

135

const container = document.getElementById("dynamic-content");

136

137

// Create parcel

138

const parcel = mountRootParcel(

139

() => import("./widget/widget.parcel.js"),

140

{

141

domElement: container,

142

initialData: { userId: 123 }

143

}

144

);

145

146

// Wait for parcel to be mounted

147

await parcel.mountPromise;

148

console.log("Parcel mounted successfully");

149

150

// Update parcel with new data

151

if (parcel.update) {

152

await parcel.update({ userId: 456, theme: "dark" });

153

console.log("Parcel updated");

154

}

155

156

// Check parcel status

157

const status = parcel.getStatus();

158

if (status === "MOUNTED") {

159

console.log("Parcel is ready");

160

}

161

162

// Unmount when done

163

await parcel.unmount();

164

console.log("Parcel unmounted");

165

}

166

```

167

168

### Application-Scoped Parcels

169

170

Applications can also mount parcels using the `mountParcel` function provided in their props:

171

172

```javascript

173

// Within an application's mount lifecycle

174

export async function mount(props) {

175

const { domElement, mountParcel } = props;

176

177

// Mount a parcel within this application

178

const widgetContainer = domElement.querySelector("#widget-container");

179

const widgetParcel = mountParcel(

180

() => import("./widget/widget.parcel.js"),

181

{

182

domElement: widgetContainer,

183

parentApp: props.name

184

}

185

);

186

187

// Store parcel reference for cleanup

188

domElement.widgetParcel = widgetParcel;

189

190

return Promise.resolve();

191

}

192

193

export async function unmount(props) {

194

const { domElement } = props;

195

196

// Clean up parcel

197

if (domElement.widgetParcel) {

198

await domElement.widgetParcel.unmount();

199

delete domElement.widgetParcel;

200

}

201

202

return Promise.resolve();

203

}

204

```

205

206

## Advanced Parcel Patterns

207

208

### Parcel Factory

209

210

Create a factory for commonly used parcels:

211

212

```javascript

213

import { mountRootParcel } from "single-spa";

214

215

class ParcelFactory {

216

static async createModal(config) {

217

const container = document.createElement("div");

218

container.className = "modal-backdrop";

219

document.body.appendChild(container);

220

221

const parcel = mountRootParcel(

222

() => import("./modal/modal.parcel.js"),

223

{

224

domElement: container,

225

...config,

226

onClose: async () => {

227

await parcel.unmount();

228

document.body.removeChild(container);

229

if (config.onClose) config.onClose();

230

}

231

}

232

);

233

234

return parcel;

235

}

236

237

static async createToast(message, type = "info", duration = 3000) {

238

const container = document.createElement("div");

239

container.className = "toast-container";

240

document.body.appendChild(container);

241

242

const parcel = mountRootParcel(

243

() => import("./toast/toast.parcel.js"),

244

{

245

domElement: container,

246

message,

247

type

248

}

249

);

250

251

// Auto-remove toast

252

setTimeout(async () => {

253

await parcel.unmount();

254

document.body.removeChild(container);

255

}, duration);

256

257

return parcel;

258

}

259

}

260

261

// Usage

262

const confirmModal = await ParcelFactory.createModal({

263

title: "Confirm Action",

264

message: "Are you sure you want to delete this item?",

265

onConfirm: () => console.log("Confirmed"),

266

onClose: () => console.log("Modal closed")

267

});

268

269

const successToast = await ParcelFactory.createToast(

270

"Item saved successfully!",

271

"success",

272

2000

273

);

274

```

275

276

### Parcel Registry

277

278

Manage multiple parcels with a registry pattern:

279

280

```javascript

281

import { mountRootParcel } from "single-spa";

282

283

class ParcelRegistry {

284

constructor() {

285

this.parcels = new Map();

286

}

287

288

async register(id, parcelConfig, parcelProps) {

289

if (this.parcels.has(id)) {

290

throw new Error(`Parcel with id "${id}" already exists`);

291

}

292

293

const parcel = mountRootParcel(parcelConfig, parcelProps);

294

this.parcels.set(id, parcel);

295

296

return parcel;

297

}

298

299

async unregister(id) {

300

const parcel = this.parcels.get(id);

301

if (!parcel) {

302

console.warn(`Parcel with id "${id}" not found`);

303

return;

304

}

305

306

await parcel.unmount();

307

this.parcels.delete(id);

308

}

309

310

get(id) {

311

return this.parcels.get(id);

312

}

313

314

async unregisterAll() {

315

const promises = Array.from(this.parcels.values()).map(parcel =>

316

parcel.unmount().catch(console.error)

317

);

318

await Promise.all(promises);

319

this.parcels.clear();

320

}

321

322

getStatus(id) {

323

const parcel = this.parcels.get(id);

324

return parcel ? parcel.getStatus() : null;

325

}

326

327

getAllStatuses() {

328

const statuses = {};

329

this.parcels.forEach((parcel, id) => {

330

statuses[id] = parcel.getStatus();

331

});

332

return statuses;

333

}

334

}

335

336

// Usage

337

const parcelRegistry = new ParcelRegistry();

338

339

// Register parcels

340

await parcelRegistry.register(

341

"notification",

342

() => import("./notification/notification.parcel.js"),

343

{ domElement: document.getElementById("notifications") }

344

);

345

346

await parcelRegistry.register(

347

"sidebar",

348

() => import("./sidebar/sidebar.parcel.js"),

349

{ domElement: document.getElementById("sidebar") }

350

);

351

352

// Later, clean up specific parcel

353

await parcelRegistry.unregister("notification");

354

355

// Or clean up all parcels

356

await parcelRegistry.unregisterAll();

357

```

358

359

## Error Handling

360

361

```javascript

362

import { mountRootParcel, addErrorHandler } from "single-spa";

363

364

// Handle parcel errors

365

addErrorHandler((error) => {

366

if (error.appOrParcelName && error.appOrParcelName.includes("parcel")) {

367

console.error("Parcel error:", error);

368

// Handle parcel-specific errors

369

}

370

});

371

372

// Safe parcel mounting with error handling

373

async function safeMountParcel(config, props) {

374

try {

375

const parcel = mountRootParcel(config, props);

376

await parcel.mountPromise;

377

return parcel;

378

} catch (error) {

379

console.error("Failed to mount parcel:", error);

380

// Clean up DOM element if needed

381

if (props.domElement && props.domElement.parentNode) {

382

props.domElement.parentNode.removeChild(props.domElement);

383

}

384

throw error;

385

}

386

}

387

```

388

389

## Types

390

391

```javascript { .api }

392

interface CustomProps {

393

[key: string]: any;

394

[key: number]: any;

395

}

396

397

type LifeCycleFn = (props: ParcelProps & CustomProps) => Promise<any>;

398

```