Giter Site home page Giter Site logo

gustafnk / h-include Goto Github PK

View Code? Open in Web Editor NEW
242.0 10.0 18.0 511 KB

Declarative client-side inclusion for the Web, using Custom Elements V1

Home Page: https://gustafnk.github.io/h-include/

License: MIT License

JavaScript 48.32% HTML 51.55% CSS 0.13%
webcomponents

h-include's Introduction

h-include.js

Declarative client-side transclusion, using Custom Elements V1. Perfect for Microfrontend architectures, in combination with server-side transclusion technologies like Edge-Side Includes.

Based on hinclude.js by @mnot.

Breaking changes in version 4.0.0:

Rename alt attribute to when-false-src to support future feature for error handling.

Breaking changes in version 3.0.0:

  • Because h-include is now using Custom Elements V1, we recommend that you update your polyfill (i.e. document-register-element) to the latest version.
  • If you have created your own custom elements that inherit from h-include, they too need to be based on Custom Elements V1. See EXTENDING.md for an example how to extend h-include.
  • The navigate attribute is broken out into the separate element <h-include-navigate>, located in lib/h-include-extensions.js.
  • Changes to @src attribute don't automatically refresh an h-include element anymore

Usage

Include an HTML resource like this:

<h-include src="/url/to/fragment.html"></h-include>

Each <h-include> element will create an AJAX request to the URL and replace the innerHTML of the element with the response of the request.

See the demo page for live examples.

Installation

Install using npm:

$ npm install h-include

Rendering Mode

By default, each include is fetched in the background and the page is updated only when they all are available.

This is bounded by a timeout, by default 2500 ms. After the timeout, h-include will show what it has and keep on listening for the remaining responses.

However, it's also possible to have responses from <h-include> elements become visible as they become available, by providing configuration:

HIncludeConfig = { mode: 'async' };

While this shows the included content quicker, it may be less visually smooth.

Custom Elements polyfill

You need to use a polyfill for enabling W3C Custom Elements for browsers not supporting Custom Elements V1.

We recommend using document-register-element (5KB minified and gzipped) as the polyfill for W3C Custom Elements.

Example:

<head>
  <script>this.customElements||document.write('<script src="//unpkg.com/document-register-element"><\x2fscript>');</script>
  <script type="text/javascript" src="/path/to/h-include.js"></script>
</head>
<body>
  ...
  <h-include src=">
  ...
</body>

Extensions

Additional extensions are located in lib/h-include-extensions.js and have lib/h-include.js as a dependency:

<script type="text/javascript" src="/lib/h-include.js"></script>
<script type="text/javascript" src="/lib/h-include-extensions.js"></script>

All extensions inherit h-include's base behavior, when applicable.

To create your own elements that inherit from <h-include>, see EXTENDING.md.

Example usage, in summary:

Resource fragments Content fragments Example
ESI ESI Short static pages
ESI h-include Dynamic web app with static content fragments (i.e. search)
ESI ESI + h‑include‑lazy Pages with homogeneous lists, lazy loaded on scroll below the fold
ESI + h‑import‑lazy ESI + h‑include‑lazy Pages with heterogeneous content, lazy loaded on scroll below the fold together with resource fragments
h‑import h‑include (etc) Sites without access to ESI

h-include-lazy

Only includes the HTML resource if the element is about to enter the viewport, by default 400 pixels margin, using the Intersection Observer API (which needs to be polyfilled).

After page load, elements in the DOM need to registered to the Intersection Observer:

<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<script type="text/javascript" src="/lib/h-include.js"></script>
<script type="text/javascript" src="/lib/h-include-extensions.js"></script>
<script>
window.addEventListener('load', function() {
  HInclude.initLazyLoad();
});
</script>

Example:

<h-include-lazy src="fragment.html"></h-include-lazy>

...


<h-include-lazy src="lazy-loaded-fragment.html"></h-include-lazy>

Example repo using plain h-include (without lazy) and the Intersection Observer API to pull in content on ‘in-view’ scroll interaction can be found here.

h-import

Request an HTML resource and include all found script and stylesheet references.

Example:

<h-import src="resource-fragment.html"></h-import>

If possible, use Edge-Side Includes (or similar) to import statically loaded resource fragments, due to performance reasons.

To load resources, h-import and h-import-lazy call HInclude.loadResources with an array of urls to resources. By default, this method delegates the resource loadin to loadjs, which needs to be on the window object. However, HInclude.loadResources can be replaced with a loader of your choice.

h-import-lazy

When lazy loading fragments, it might be the case that additional style and script resources need to be loaded as well. For example, think of a lazy loaded footer with rather specific styling. For these scenarios, use h-import-lazy.

After page load, elements in the DOM need to registered to the Intersection Observer:

<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<script type="text/javascript" src="/lib/h-include.js"></script>
<script type="text/javascript" src="/lib/h-include-extensions.js"></script>
<script>
window.addEventListener('load', function() {
  HInclude.initLazyLoad();
});
</script>

Example:

<h-include-lazy src="fragment.html"></h-include-lazy>

...


<h-import-lazy src="lazy-loaded-resource-fragment.html"></h-import-lazy>

h-include-navigate

Use <h-include-navigate> to let link navigation events be captured by the element itself, which changes the src attribute and triggers a refresh.

Use target="_top" to let link inside h-include behave as a normal link.

Helper function: HInclude.initLazyLoad

By default, the selector for HInclude.initLazyLoad is 'h-include-lazy, h-import-lazy' and the Intersection Observer rootMargin and threshold default values are 400px 0px and 0.01 respectively. These can be overridden:

HInclude.initLazyLoad('css style selector', {rootMargin: '200px 0', threshold: 0.2});

Helper function: HInclude.loadResources

Load an array of script and stylesheet resources (to be overridden).

Advanced usage

Conditional inclusion using when

When offers a way of using a predicate for inclusion. It also features an optional when-false-src attribute that functions as the source of inclusion given that the predicate fails.

<h-include src="logged-in.html" when="org.project.predicateFunction" when-false-src="log-in.html"></h-include>

The method specified in the when attribute may be namespaced and needs to return true for the inclusion of the url specified in the src attribute to occur.

Request errors and alternative inclusion using alt

The alt attribute functions as an alternative source of inclusion should the url result in a request error.

<h-include src="unavailable.html" alt="alternative.html"></h-include>

Refresh method

Refresh an element by using the refresh() method:

const element = document.getElementsByTagName('h-include')[0];
element.refresh();

Media queries

Use media queries to have different fragments for different devices:

<h-include media="screen and (max-width: 600px)" src="small.html"></h-include>
<h-include media="screen and (min-width: 601px)" src="large.html"></h-include>

<h-include> will not listen to changes to screen orientation or size.

Fragment extraction

Include an HTML resource and extract a fragment of the response by using a selector:

<h-include src="..." fragment=".container"></h-include>

XMLHttpRequest.withCredentials

Enable XMLHttpRequest.withCredentials:

<h-include src="..." with-credentials></h-include>

Configuration

Set buffered include timeout (default is 2500 ms):

HIncludeConfig = { timeout: 10000 };

Set include mode to async (default is buffered):

HIncludeConfig = { mode: 'async' };

Throw if caught in an infinite include loop (default is false):

HIncludeConfig = { checkRecursion: true };

If checkRecursion is true, h-include will traverse the DOM upwards to find another h-include element with the same src attribute, until the root node. This operation takes a few CPU cycles per h-include, which is why it's not enable by default.

Error Handling

If fetching the included URL results in a 404 Not Found status code, the class of the include element will be changed to include_404. Likewise, a 500 Server Error status code will result in the include element’s class being changed to include_500.

Browser support

All modern browsers and IE down to IE10 are supported. If you find something quirky, please file an issue.

HTTP/2 improves XHR performance

Browsers with HTTP/2 are using HTTP/2 for xhr requests as well. So, if both the server and the current browser supports HTTP/2, all requests made with h-include will go through the same TCP connection, given that they have the same origin.

Bundler

Parcel

With this plugin, Parcel searches and optimizes all h-include tags https://github.com/joserochadocarmo/parcel-plugin-h-include

FAQ

Please see the FAQ for some frequently asked questions.

h-include's People

Contributors

acasademont avatar andreascf avatar dependabot[bot] avatar fabiofabbrucci avatar gustafnk avatar hallgrenx avatar joserochadocarmo avatar mnot avatar nicolasdelfino avatar olleolleolle avatar rickardandersson avatar rjkip avatar shogun70 avatar yberkholz avatar yoavweiss avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

h-include's Issues

Update contents in respone to @src changes

Currently the contents installed into the <h-include> only match the @src URL at the moment the custom-element binding is attached. If the binding could listen for changes to @src and update the contents accordingly then <h-include> could be used something like a light-weight <iframe>.

Figure out a way to do "lazy loading" of h-include elements

I think it would be useful to have a way to lazy load h-include elements. It should be possible to configure h-include so that elements "below the fold" wait with loading themselves until they are visible in the viewport (by doing a refresh on themselves).

To support this, we need to:

  • add a configuration that disables the initial loading of an h-include element
  • figure out some code that does the viewport detection. I haven't done the research on what would be the best strategy for this, considering older browsers, mobile browsers, and performance. I'd be happy to load all h-include elements for browsers where viewport visibility detection is not good enough performance-wise
  • when #27 is solved, we should add an example element to the repo

Move PhantomJS tests to cross browser testing tool

It would be awesome to be able to run the tests in as many browsers as possible. What is the best free tool today for that? And we need to rip out the tight PhantomJS dependency then, replacing it with some browser instrumentation library.

document.registerElement is deprecated

My browser (Chrome) complains that document.registerElement is deprecated and will be removed around March 2019.

Is there any ongoing work to use the suggested method window.customerElements.define instead?

image

Needs recursion check

If a HTML page uses h-include to include itself then the included contents will also initiate another include of itself and then another, ad infinitum. For example:

<h-include src=""></h-include>

IMO a <h-include> instance should abort if it's @src is the same URL as one of its ancestor <h-include>s or the page URL.

CI: timeout connecting to WebDriver Server

Build which failed: https://travis-ci.org/gustafnk/h-include/builds/384772141

npm run server > /dev/null 2>&1 &
PORT=8080 node index.js 
(node:2807) [DEP0022] DeprecationWarning: os.tmpDir() is deprecated. Use os.tmpdir() instead.
Starting browser firefox latest on Windows
Somewhere an unhandled exception screams
Error: Timed out waiting for the WebDriver server at http://127.0.0.1:59755/hub
    at onError (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/http/util.js:87:11)
    at ManagedPromise.invokeCallback_ (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/lib/promise.js:1379:14)
    at TaskQueue.execute_ (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/lib/promise.js:2913:14)
    at TaskQueue.executeNext_ (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/lib/promise.js:2896:21)
    at asyncRun (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/lib/promise.js:2775:27)
    at /home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/lib/promise.js:639:7
    at process._tickCallback (internal/process/next_tick.js:68:7)
From: Task: WebDriver.createSession()
    at Function.createSession (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/lib/webdriver.js:329:24)
    at new Driver (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/firefox/index.js:386:38)
    at Builder.build (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/builder.js:467:16)
    at Builder.buildAsync (/home/travis/build/gustafnk/h-include/node_modules/selenium-webdriver/builder.js:504:23)
    at Object.start (/home/travis/build/gustafnk/h-include/test/framework.js:36:8)
    at testBrowser (/home/travis/build/gustafnk/h-include/test/index.js:65:19)
    at /home/travis/build/gustafnk/h-include/test/index.js:47:11
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:746:11)
    at startup (internal/bootstrap/node.js:238:19)
make: *** [test] Error 1
npm ERR! Test failed.  See above for more details.

Hook calling conventions

If I have hincElement which is a reference to a <h-include> element, and I define a hook such as

hincElement.createContainer = function(req) {
    var mode = this.getAttribute('mode');
    // Implement "mode" dependent behavior
    ...
   return container;
}

then in my hook this should refer to the <h-include> element.
Hooks which are present on the element should be called similar to:

element[hookName](args)

If the hook is matched by a static method on hinclude (e.g. createContainer -> create_container) then for consistency the static method should be passed the element as the first argument:

hinclude[hook_name](element, args...)

When rapidly refreshing only first `@src` is applied

If I change @src and call refresh() twice within too short an interval then the second @src isn't applied. I also (often but not always) see this if the <h-include> in the markup has an initial @src and this is changed during window.onload.

See attached files.

refresh.zip

Improved error handling

(From #20 (comment))

  • basically any place in show_content() which returns or throws before the bottom of the function.
  • fragment not found errors log a console warning but don't change the class or content.
    Some user-interaction might have triggered a @src change but the contents and appearance
    still matches the previous @src.
    I wonder if this should be considered a 404 error?
  • recursion error (maybe a 412 error is appropriate?)

Relative URLs in included content should be made absolute

Say some included content contains relative URLs such as the following:

<a href="rel/path/to/other-page.html">Other page</a>
<img src="my-images/img.jpg" />

Should the respective @href and @src be relative to the including page or the included content?
I think it is the included content. This will require absolutizing relative URLs based on the URL of the included content before inserting.

Missing license info

Hi,
there is not LICENSE - file provided within the repository.
Under which license h-include is published?

Kind regards,
Ronny

Expose `proto` as the externally accessible `hinclude.prototype`

This will allow other other custom-elements to inherit from hinclude,
as well as allowing page authors to define global hooks, for example

hinclude.prototype.extractFragment = function(container) {
    var mode = this.getAttribute('mode');
    // choose different pre-processing based on "mode"
   return newContainer;
}

`@class` is clobbered - this should be configurable

If a <h-include> element already has @class then its value is replaced by "include_200" (or similar).

I think it would be more appropriate to only replace (whitespace-separated) tokens within @class that match /^include_\d+$/i.

Ideally there is a way to intercept success and failure handling to implement a completely different behavior, leaving the current behavior as the (preventable) default action.

I also note (so I don't forget) that there are a few code paths where errors in transclusion won't be indicated in any way:

  • basically any place in show_content() which returns or throws before the bottom of the function.
  • fragment not found errors log a console warning but don't change the <h-include> class or content.
    Some user-interaction might have triggered a @src change but the contents and appearance
    still matches the previous @src.
    I wonder if this should be considered a 404 error?
  • recursion error (maybe a 412 error is appropriate?)

Add onLoad method

This can be a nice little enhancement for consumers. The default behaviour for onLoad should be no-op, so there's no need to call super when using h-include[-*] directly without inheritance chains.

h-import: Invalid <link> and <script> elements aren't properly removed from url array

Inside of createContainer() there is this code:

var urls = importableElements.map(function(element) {
      if (element.tagName === 'SCRIPT') {
        return element.src;
      } else if (
          element.tagName === 'LINK' &&
          element.rel &&
          element.rel.toLowerCase() === 'stylesheet') {
        return element.href;
      }
    });

For <link> elements that don't have "rel="stylesheet", this leaves the value undefined in the array. When the urls list is passed to loadjs, this throws an exception. The undefined values should be removed from the array before calling loadResources().

Refactor test cases to have more control by themselves

I think we should refactor the test cases to not export a ['dom element', 'expected value] pair, but instead let each test perform this check locally. Think for example how a common Jasmine test would look like.

If we perform this refactoring, we can assert on things not being in the DOM as well, which I think would be valuable.

Loading more than one resource fails

When loading more than one external resource there is unexpected behaviour:

Current Behaviour

JS

require('h-include/lib/h-include');
require('h-include/lib/h-include-extensions');

window.addEventListener('load', function() {
    HInclude.initLazyLoad();
});

html:

<h-include-lazy src="/returns-500" alt="/nice-error">loading</h-include-lazy><br>
<h-include-lazy src="/returns-500" alt="/nice-error">loading</h-include-lazy><br>
<h-include-lazy src="/returns-500" alt="/nice-error">loading</h-include-lazy>

Page output:

nice error
loading
loading

Expected Behaviour

Page output:

nice error
nice error
nice error

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.