Skip to content

Commit

Permalink
Merge pull request #623 from smapiot/develop
Browse files Browse the repository at this point in the history
Release 1.2.0
  • Loading branch information
FlorianRappl authored Aug 28, 2023
2 parents 513bb2d + 7cee89e commit 697e698
Show file tree
Hide file tree
Showing 18 changed files with 165 additions and 29 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Piral Changelog

## 1.2.0 (tbd)

- Fixed issue with `loader-utils` version
- Fixed issue with potential URL flickering using `piral-ng`
- Fixed build issue in codegen of `piral-core` on Windows (#619)
- Updated `importmap` with `exclude` key allowing exclusions
- Added support for `dependencySymbols` in `piral-blazor`
- Added option to stop module teardown via `flags` parameter in `piral-ng`
- Added abort `signal` to `piral-fetch` options (#621)

## 1.1.0 (July 25, 2023)

- Fixed retrieval of dep versions not exporting their *package.json*
Expand Down
28 changes: 27 additions & 1 deletion docs/concepts/I08-importmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ In general, Piral distinguishes between three types of dependencies:
- centrally shared dependencies, i.e., dependencies that are already bundled with an app shell, but otherwise not optimized
- distributed shared dependencies, i.e., dependencies that are shipped (not bundled) unoptimized with a pilet - such that other pilets can use them, too

Besides the standard `imports` key of the importmaps specification, the `piral-cli` also allows a new key called `inherit`. This one can be used to specify packages coming with an importmap.
Besides the standard `imports` key of the importmaps specification, the `piral-cli` also allows new keys called `inherit` and `exclude`. While `inherit` can be used to specify packages coming with an importmap the `exclude` property can be used to exclude dependencies from inherited importmaps.

## Piral Instance Importmaps

Expand Down Expand Up @@ -98,6 +98,16 @@ This way, you can either create reusable packages containing importmaps or easil
}
```

Alternatively, you can still inherit from a package but exclude certain packages using the `exclude` keyword. For instance, using the following notation `piral-base` and `piral-core` are still inherited (importing, among other things, `react`, `react-dom`, `react-router`, and `react-router-dom`), however, `react-dom` is being excluded from these imports.

```json
{
"imports": {},
"inherit": ["piral-base", "piral-core"],
"exclude": "react-dom"
}
```

Classically, all centrally shared dependencies are delivered with the app shell ("single bundle"). In some case it might make sense to load a shared dependency only when needed. To enable this behavior you can suffix the dependency with the `?` character - indicating that the shared dependency is optional:

```json
Expand Down Expand Up @@ -176,6 +186,18 @@ Consequently, the following

will add `emojis-list` as a distributed dependency (adding a side-bundle for the dependency to the assets) and treat any dependency exposed by the `my-app-shell` as a centrally shared dependency.

If you want to bundle in a dependency that is already declared as a centrally shared dependency in the app shell you can use the `exclude` keyword. For instance, the following call declares the packages shared from `my-app-shell` as external with exception of the `react` package:

```json
{
"imports": {},
"inherit": ["my-app-shell"],
"exclude": ["react"]
}
```

If a dependency is both excluded and marked as a distributed dependency in the `imports` section then the latter wins - explicit imports are always more relevant than exclusions from the inheritance chain.

## Importmap Notation

The importmap notation also allows some special syntax that can be used to set up some rules for the dependency. For instance,
Expand Down Expand Up @@ -277,3 +299,7 @@ The precedence of the lookup is:

1. See if `<inherited-name>/package.json` exists. Follow up on any `importmap` property from the file if exists.
2. See if `<inherited-name>` exists. Take it if it exists. **Note**: This has to be an importmap (JSON) then.

## Exclusions

Exclusions work across all inherited packages - directly and indirectly. This way, you can also make general statements about bundling. For instance, you might want to put a dependency in `exclude` that you *want* to see bundled - independent if it appears (right now) in the inherited importmaps or not.
13 changes: 9 additions & 4 deletions src/converters/piral-blazor/src/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import type { BlazorDependencyLoader, BlazorRootConfig } from './types';
const loadedDependencies = (window.$blazorDependencies ??= []);
const depsWithPrios = (window.$blazorDependencyPrios ??= []);

function toPdb(url: string) {
const front = url.substring(0, url.length - 4);
return `${front}.pdb`;
}

export function createDependencyLoader(convert: ReturnType<typeof createConverter>) {
const definedBlazorReferences: Array<string> = [];
const loadedBlazorPilets: Array<string> = [];
Expand Down Expand Up @@ -41,9 +46,8 @@ export function createDependencyLoader(convert: ReturnType<typeof createConverte

const dependencies = references.filter((m) => m.endsWith('.dll'));
const dllUrl = dependencies.pop();
const piletName = dllUrl.substring(0, dllUrl.length - 4);
const piletPdb = `${piletName}.pdb`;
const pdbUrl = references.find((m) => m === piletPdb);
const pdbUrl = toPdb(dllUrl);
const dependencySymbols = dependencies.map(toPdb).filter(dep => references.includes(dep));
const id = Math.random().toString(26).substring(2);

await loadBlazorPilet(id, {
Expand All @@ -52,9 +56,10 @@ export function createDependencyLoader(convert: ReturnType<typeof createConverte
config: JSON.stringify(meta.config || {}),
baseUrl: meta.basePath || dllUrl.substring(0, dllUrl.lastIndexOf('/')).replace('/_framework/', '/'),
dependencies,
dependencySymbols: capabilities.includes('dependency-symbols') ? dependencySymbols : undefined,
satellites,
dllUrl,
pdbUrl,
pdbUrl: references.includes(pdbUrl) ? pdbUrl : undefined,
});

loadedBlazorPilets.push(id);
Expand Down
1 change: 1 addition & 0 deletions src/converters/piral-blazor/src/interop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export interface PiletData {
baseUrl: string;
satellites?: Record<string, Array<string>>;
dependencies: Array<string>;
dependencySymbols?: Array<string>;
}

export function loadBlazorPilet(id: string, data: PiletData) {
Expand Down
8 changes: 6 additions & 2 deletions src/converters/piral-ng/src/RoutingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export class RoutingService implements OnDestroy {
const skipIds: Array<number> = [];
const nav = this.context.navigation;

const queueNavigation = (url: string) => {
window.requestAnimationFrame(() => nav.push(url));
};

this.dispose = nav.listen(({ location }) => {
const path = location.pathname;

Expand All @@ -84,7 +88,7 @@ export class RoutingService implements OnDestroy {
}

if (routerUrl !== locationUrl) {
nav.push(routerUrl);
queueNavigation(routerUrl);
}
} else if (e.type === 0 && skipNavigation) {
skipIds.push(e.id);
Expand All @@ -97,7 +101,7 @@ export class RoutingService implements OnDestroy {
const routerUrl = e.routerEvent.url;

if (routerUrl !== locationUrl) {
nav.push(routerUrl);
queueNavigation(routerUrl);
}
} else {
skipIds.splice(index, 1);
Expand Down
6 changes: 3 additions & 3 deletions src/converters/piral-ng/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function prepareBootstrap(
throw new Error('The lazy loaded module does not `default` export a NgModule class.');
}

defineModule(result.default, moduleOrComponent.opts);
defineModule(result.default, moduleOrComponent.opts, moduleOrComponent.flags);
return findModule(result.default);
});
}
Expand Down Expand Up @@ -59,8 +59,8 @@ export async function bootstrap<TProps extends BaseComponentProps>(
props: BehaviorSubject<TProps>,
context: ComponentContext,
): Promise<Disposable> {
const [selectedModule, ngOptions, component] = result;
const ref = await startup(selectedModule, context, ngOptions);
const [selectedModule, ngOptions, ngFlags, component] = result;
const ref = await startup(selectedModule, context, ngOptions, ngFlags);

if (ref) {
ref.instance.attach(component, node, props);
Expand Down
20 changes: 14 additions & 6 deletions src/converters/piral-ng/src/module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { PiletApi } from 'piral-core';
import type { BehaviorSubject } from 'rxjs';
import type { NgOptions, ModuleInstanceResult } from './types';
import type { NgOptions, ModuleInstanceResult, NgModuleFlags } from './types';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import {
ApplicationRef,
ComponentFactoryResolver,
ComponentRef,
CUSTOM_ELEMENTS_SCHEMA,
Inject,
NgModule,
NgZone,
} from '@angular/core';
Expand All @@ -21,6 +22,7 @@ interface ModuleDefinition {
module: any;
components: Array<any>;
opts: NgOptions;
flags: NgModuleFlags;
}

const availableModules: Array<ModuleDefinition> = [];
Expand All @@ -45,7 +47,12 @@ function instantiateModule(moduleDef: ModuleDefinition, piral: PiletApi) {
private appRef: ApplicationRef;
private refs: Array<[any, HTMLElement, ComponentRef<any>]> = [];

constructor(private resolver: ComponentFactoryResolver, private zone: NgZone, public routing: RoutingService) {}
constructor(
private resolver: ComponentFactoryResolver,
private zone: NgZone,
public routing: RoutingService,
@Inject('NgFlags') private flags: NgModuleFlags,
) {}

ngDoBootstrap(appRef: ApplicationRef) {
this.appRef = appRef;
Expand Down Expand Up @@ -81,7 +88,7 @@ function instantiateModule(moduleDef: ModuleDefinition, piral: PiletApi) {
}
}

if (this.refs.length === 0) {
if (!this.flags?.keepAlive && this.refs.length === 0) {
teardown(BootstrapModule);
}
}
Expand All @@ -95,7 +102,7 @@ export function activateModuleInstance(moduleDef: ModuleDefinition, piral: Pilet
moduleDef.active = instantiateModule(moduleDef, piral);
}

return [moduleDef.active, moduleDef.opts];
return [moduleDef.active, moduleDef.opts, moduleDef.flags];
}

export function getModuleInstance(component: any, standalone: boolean, piral: PiletApi) {
Expand Down Expand Up @@ -140,14 +147,15 @@ export function findModule(module: any) {
return availableModules.find((m) => m.module === module);
}

export function defineModule(module: any, opts: NgOptions = undefined) {
export function defineModule(module: any, opts: NgOptions = undefined, flags: NgModuleFlags = undefined) {
const [annotation] = getAnnotations(module);

if (annotation) {
availableModules.push({
active: undefined,
components: findComponents(annotation.exports),
module,
flags,
opts,
});
} else if (typeof module === 'function') {
Expand All @@ -156,7 +164,7 @@ export function defineModule(module: any, opts: NgOptions = undefined) {
};

return (selector: string) => ({
component: { selector, module, opts, state },
component: { selector, module, opts, flags, state },
type: 'ng' as const,
});
}
Expand Down
8 changes: 5 additions & 3 deletions src/converters/piral-ng/src/startup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentContext } from 'piral-core';
import type { NgOptions } from './types';
import type { NgModuleFlags, NgOptions } from './types';
import {
createPlatformFactory,
enableProdMode,
Expand Down Expand Up @@ -31,11 +31,12 @@ const customPlatformDynamicFactory = createPlatformFactory(platformCoreDynamic,
]);
const runningModules: Array<[any, NgModuleInt, PlatformRef]> = [];

function startNew(BootstrapModule: any, context: ComponentContext, ngOptions?: NgOptions) {
function startNew(BootstrapModule: any, context: ComponentContext, ngOptions?: NgOptions, ngFlags?: NgModuleFlags) {
const path = context.publicPath || '/';
const platform = customPlatformDynamicFactory([
{ provide: 'Context', useValue: context },
{ provide: APP_BASE_HREF, useValue: path },
{ provide: 'NgFlags', useValue: ngFlags },
]);
const id = getId();
const zoneIdentifier = `piral-ng:${id}`;
Expand Down Expand Up @@ -87,6 +88,7 @@ export function startup(
BootstrapModule: any,
context: ComponentContext,
ngOptions?: NgOptions,
ngFlags?: NgModuleFlags,
): Promise<void | NgModuleInt> {
const runningModule = runningModules.find(([ref]) => ref === BootstrapModule);

Expand All @@ -100,7 +102,7 @@ export function startup(
}
}

return startNew(BootstrapModule, context, ngOptions);
return startNew(BootstrapModule, context, ngOptions, ngFlags);
}

if (process.env.NODE_ENV === 'development') {
Expand Down
16 changes: 13 additions & 3 deletions src/converters/piral-ng/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ declare module 'piral-core/lib/types/custom' {
}
}

export interface NgModuleFlags {
/**
* If set to true prevents the module from being disposed.
*/
keepAlive?: boolean;
}

/**
* Options passed through to Angular `bootstrapModule`.
*
Expand All @@ -18,7 +25,7 @@ declare module 'piral-core/lib/types/custom' {
*/
export type NgOptions = Parameters<PlatformRef['bootstrapModule']>[1];

export type ModuleInstanceResult = [any, NgOptions];
export type ModuleInstanceResult = [any, NgOptions, NgModuleFlags];

export type PrepareBootstrapResult = [...ModuleInstanceResult, any];

Expand All @@ -39,6 +46,7 @@ export interface NgLazyType {
selector: string;
module: () => Promise<{ default: Type<any> }>;
opts: NgOptions;
flags: NgModuleFlags;
state: any;
}

Expand All @@ -60,15 +68,17 @@ export interface NgModuleDefiner {
* Defines the module to use when bootstrapping the Angular pilet.
* @param ngModule The module to use for running Angular.
* @param opts The options to pass when bootstrapping.
* @param flags The flags to use when dealing with the module.
*/
<T>(module: Type<T>, opts?: NgOptions): void;
<T>(module: Type<T>, opts?: NgOptions, flags?: NgModuleFlags): void;
/**
* Defines the module to lazy load for bootstrapping the Angular pilet.
* @param getModule The module lazy loader to use for running Angular.
* @param opts The options to pass when bootstrapping.
* @param flags The flags to use when dealing with the module.
* @returns The module ID to be used to reference components.
*/
<T>(getModule: LazyType<T>, opts?: NgOptions): NgComponentLoader;
<T>(getModule: LazyType<T>, opts?: NgOptions, flags?: NgModuleFlags): NgComponentLoader;
}

export interface NgComponent {
Expand Down
8 changes: 6 additions & 2 deletions src/framework/piral-core/src/tools/codegen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// this file is bundled, so the references here will not be at runtime (i.e., for a user)
import { getModulePath } from 'piral-cli/src/external/resolve';
import { readFileSync, existsSync } from 'fs';
import { resolve, relative, dirname } from 'path';
import { resolve, relative, dirname, sep, posix } from 'path';

function findPackagePath(moduleDir: string) {
const packageJson = 'package.json';
Expand Down Expand Up @@ -66,7 +66,11 @@ function getIdentifiers(root: string, packageName: string) {
function getModulePathOrDefault(root: string, origin: string, name: string) {
try {
const absPath = getModulePath(root, name);
const path = relative(origin, absPath);
const relPath = relative(origin, absPath);

// The relative path is to be used in an import statement,
// so it should be normalized back to use posix path separators.
const path = relPath.split(sep).join(posix.sep);
return path;
} catch {
return name;
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/piral-fetch/src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function httpFetch<T>(config: FetchConfig, path: string, options: FetchOp
const baseInit = config.default || {};
const baseHeaders = baseInit.headers || {};
const baseUrl = config.base || location.origin;
const { method = 'get', body, headers = {}, cache = baseInit.cache, mode = baseInit.mode, result = 'auto' } = options;
const { method = 'get', body, headers = {}, cache = baseInit.cache, mode = baseInit.mode, result = 'auto', signal } = options;
const json =
Array.isArray(body) ||
typeof body === 'number' ||
Expand All @@ -25,6 +25,7 @@ export function httpFetch<T>(config: FetchConfig, path: string, options: FetchOp
},
cache,
mode,
signal,
};

if (json) {
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/piral-fetch/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export interface FetchOptions {
* @default 'auto'
*/
result?: 'auto' | 'json' | 'text';
/**
* Sets the Abort Signal to cancel the request.
*/
signal?: AbortSignal;
}

export interface FetchResponse<T> {
Expand Down
1 change: 1 addition & 0 deletions src/tooling/piral-cli-webpack5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"express": "^4.17.1",
"html-entities": "^1.2.0",
"html-webpack-plugin": "^5.5.3",
"loader-utils": "^2.0.0",
"mini-css-extract-plugin": "^2.7.6",
"parcel-codegen-loader": "^1.0.0",
"querystring": "^0.2.0",
Expand Down
Loading

0 comments on commit 697e698

Please sign in to comment.