How to migrate from a multipage app to Piral

385 Views Asked by At

We run a few ASP.NET Core applications that do come with some pages. These are all "classic" multipage applications.

For our new portal we've decided to go with piral. While we add a couple of new modules we also want to use the existing applications.

How can we integrate a multipage application in piral or in a clientside (SPA) micro-frontend?

One example for a multipage app looks like (hosted on some address like https://myexample.com/home)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Privacy Policy - DotnetApp</title>
    <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" href="/">DotnetApp</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" href="/">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" href="/Home/Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            <h1>Privacy Policy</h1>

<p>Use this page to detail your site's privacy policy.</p>

        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2020 - DotnetApp - <a href="/Home/Privacy">Privacy</a>
        </div>
    </footer>
    <script src="/lib/jquery/dist/jquery.min.js"></script>
    <script src="/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="/js/site.js?v=4q1jwFhaPaZgr8WAUSrux6hAuh0XDg9kPS3xIVq36I0"></script>

</body>
</html>

We don't need to have all scripts - most of them are just there due to the boilerplate. Actually we can also run without all of them.

1

There are 1 best solutions below

2
On BEST ANSWER

Migration of an MPA to Piral can be done in multiple ways.

  1. Create a wrapper pilet that exposes the different SSRed parts via iframes
  2. Create a wrapper pilet that exposes the different SSRed parts via fragments which are dynamically obtained
  3. Convert your Razor views into React parts and have a split between FE/BE; put the React parts in a pilet

I don't know how complicated you application is, but maybe (1) would be the easiest to get started (especially if its, e.g., just a single page).

Let's see how (2) may be implemented:

import * as React from 'react';

export function setup(app) {
    const connect = app.createConnector(() => fetch('https://myexample.com/home').then(res => res.text()));
    app.registerPage('/myexample', connect(({ data }) => {
        const __html = data;
        return <div dangerouslySetInnerHTML={{ __html }} />;
    }));
}

Here, we lazy load content from https://myexample.com/home and display it in the component (which will be a page).

There are a couple of things you should watch out for:

  • In the best case detect the fetch (or send a special header) and only return a fragment instead of the full response
  • If stylesheets are needed either "fish" them out of the response or integrate them upfront (see next point)
  • The code above has no special SPA link handling. Thus all links are relative to the given page URL and they are making a full transition ...
  • JavaScript would not be loaded / working in the given solution

Now regarding a stylesheet you could do (among other things):

import './my-style.css';
import * as React from 'react';

export function setup(app) {
  // ...
}

where my-style.css looks like:

@import url(https://myexample.com/css/site.css);

We could have more URLs etc. here, but I leave that out on purpose. Keep in mind that the previously shown way could introduce collisions. So either "download" the sheet upfront and prefix ("scope") it, or use it all in a shadow DOM solution (shadow DOM is always "free" of the parent DOM's style and needs to import its own stylesheets).

For the link handling what you can do: Use a layout effect, get all a elements via a selector, attach an event and cancel the original navigation. Use the history context instead.

In code this looks similar to:

import * as React from 'react';
import { useHistory } from 'react-router';

export function setup(app) {
    const connect = app.createConnector(() => fetch('https://myexample.com/home').then(res => res.text()));
    app.registerPage('/myexample', connect(({ data }) => {
        const __html = data;
        const container = React.useRef();
        const history = useHistory();
        React.useLayoutEffect(() => {
            if (container.current) {
                const anchors = container.current.querySelectorAll('a[href]');
                Array.prototype.forEach.call(anchors, anchor => {
                    anchor.addEventListener('click', ev => {
                        ev.preventDefault();
                        history.push(anchor.getAttribute('href'));
                    });
                });
            }
        }, []);
        return <div dangerouslySetInnerHTML={{ __html }} ref={container} />;
    }));
}