Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

user template replaces proposal #1152

Closed
wants to merge 5 commits into from

Conversation

arve0
Copy link

@arve0 arve0 commented Apr 12, 2020

This PR gives the user control after page render, but before res.end, so that the HTML output can be altered. This can be handy to:

  • replace text in the body template
  • omitting script when a header is set
  • alter the base href to be absolute for certain user-agents
  • etc.

Multiple issues falls into this category, "let user control HTML output", like #657, #984, #1034, #1036, #1066, etc.

Today this is possible to some degree, by monkey-patching res.end(body), which is the last call in handle_page:

function (req, res, next) {
    if (req.get('user-agent').toLowerCase().includes("msie")) {
        let resEnd = res.end;

        res.end = function () {
            let body = arguments[0]
            if (typeof body === "string") {
                // absolute base href
                let base = `<base href="${req.protocol}://${req.get('host')}${req.baseUrl}/">`
                body = body.replace(/<base href="[^"]+">/, base)
                // do not send scripts
                body = body.replace(/<script.*\/script>/g, '')

                arguments[0] = body
            }

            resEnd.call(this, ...arguments)
        }
    }

    next()
},

Though, that may get clunky depending on your middleware call order.

This proposal spins of @vilarfg's #1037, but also lets the user:

  • replace anything (replacing is not bound to %sapper.key%)
  • override system replacers (by replacing %sapper.key% first)

Implementation
Before responding with the page, replace text in the page body with "replacers". A "replacer" is a reducer (TemplateReducer) which is called with the template body and returns the body back.

Replacers are run in the order they are defined and system replacers will
run last: base href → head → styles → html → script.

Examples

Omit javascript for Internet Explorer:

// handler before sapper middleware
(req, res, next) => {
	if (req.get('user-agent').toLowerCase().includes("msie")) {
		res.replacers = res.replacers || []
		// remove script from page content, return content back
		res.replacers.push((body) => body.replace("%sapper.scripts%", ""))
	}
	next()
}

Insert footer notice when unsupported browser:

(req, res, next) => {
	// matchesUA: https://github.com/browserslist/browserslist-useragent
	if (!matchesUA(req.get('user-agent'))) {
		res.replacers = res.replacers || [];
		res.replacers.push((body) =>
			body.replace('</body>', `${unsupportedBrowserNotice}</body>`)
		);
	}
	next();
}

@arve0
Copy link
Author

arve0 commented Apr 12, 2020

Forgot to run the tests...

A "replacer" is a reducer which is called with the page content and
returns the page content. A replacer can:

- alter the base href for certain user-agents
- replace text in the body template
- omitting script when a header is set
- etc.

For example, omitting javascript for Internet Explorer:

```js
// handler before sapper middleware
(req, res, next) => {
        if (req.get('user-agent').toLowerCase().includes("msie")) {
                res.replacers = res.replacers || []
                res.replacers.push((ctx) => (ctx.script = "", ctx))
        }
        next()
}
```

or inserting a footer notice on unsupported browsers:

```js
(req, res, next) => {
        // matchesUA: https://github.com/browserslist/browserslist-useragent
        if (!matchesUA(req.get('user-agent'))) {
                res.replacers = res.replacers || []
                res.replacers.push((ctx) => (ctx.body.replace(/<\/body>/, `${unsupportedBrowserNotice}</body>`), ctx))
        }
        next()
}
```

Replacers are run in the order they are defined. System replacers will
run last in order:

→ base href
→ head
→ styles
→ html
→ script.
Remove `PageContent` and simply do replacements on the template body
only. I find it hard to find examples of reducers that alters script,
style, base href, etc, that cannot be done by replacing `%sapper.key%`
first.
@arve0
Copy link
Author

arve0 commented Apr 13, 2020

Not sure why the redirect tests fails on appveyor:

     Error: net::ERR_UNSAFE_PORT at http://localhost:2049/redirect-from

1830b31 was ok, and test/apps/redirects/ is not touched.

@benmccann
Copy link
Member

@arve0 would the approach @antony mentioned of using regexp-replace work for you?

@arve0
Copy link
Author

arve0 commented May 15, 2020

I believe that does not work when running in dev- or live-mode, right?

@benmccann
Copy link
Member

yeah, you're right. he said he did it in a script after build

@benmccann benmccann added this to Configuration in Roadmap Triage Aug 20, 2020
@happycollision
Copy link
Contributor

Man. There are at least 3 PRs open that try to accomplish this problem in different ways. I wish I had realized that before I started on mine! FWIW, you can have a look at #1642 and see what the pros and cons between the two different approaches might be. 😅

@benmccann
Copy link
Member

Thanks for putting together this PR! Sapper never had a good method of handling configuration, which made it difficult to know how to integrate this feature. However, that's been addressed in SvelteKit by adding a svelte.config.cjs file and adapters. I'm going to go ahead and close this PR since we won't be adding the feature to Sapper. You might like to check out SvelteKit instead. Someone's sent a PR for this feature already actually sveltejs/kit#641

@benmccann benmccann closed this Mar 24, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
No open projects
Roadmap Triage
Configuration
Development

Successfully merging this pull request may close these issues.

None yet

3 participants