80 votes

Inlining HTML Modules ECMAScript

J'ai expérimenté un nouveau support de module natif ECMAScript qui a récemment été ajouté aux navigateurs. C'est agréable de pouvoir enfin importer des scripts directement et proprement depuis JavaScript.  /example.html

49voto

Jeremy Banks Points 32470

Piratage Ensemble Notre Propre import from '#id'

Les exportations/importations entre inline scripts ne sont pas pris en charge nativement, mais c'était un exercice amusant pour pirater une application pour mes documents. Code-ont joué au golf vers le bas à un petit bloc, je l'utilise comme ceci:

<script type="module" data-info="https://stackoverflow.com/a/43834063">let l,e,t
='script',p=/(from\s+|import\s+)['"](#[\w\-]+)['"]/g,x='textContent',d=document,
s,o;for(o of d.querySelectorAll(t+'[type=inline-module]'))l=d.createElement(t),o
.id?l.id=o.id:0,l.type='module',l[x]=o[x].replace(p,(u,a,z)=>(e=d.querySelector(
t+z+'[type=module][src]'))?a+`/* ${z} */'${e.src}'`:u),l.src=URL.createObjectURL
(new Blob([l[x]],{type:'application/java'+t})),o.replaceWith(l)//inline</script>

<script type="inline-module" id="utils">
  let n = 1;
  
  export const log = message => {
    const output = document.createElement('pre');
    output.textContent = `[${n++}] ${message}`;
    document.body.appendChild(output);
  };
</script>

<script type="inline-module" id="dogs">
  import {log} from '#utils';
  
  log("Exporting dog names.");
  
  export const names = ["Kayla", "Bentley", "Gilligan"];
</script>

<script type="inline-module">
  import {log} from '#utils';
  import {names as dogNames} from '#dogs';
  
  log(`Imported dog names: ${dogNames.join(", ")}.`);
</script>

Au lieu de <script type="module">, nous devons définir nos éléments de script à l'aide d'un type personnalisé comme <script type="inline-module">. Cela empêche le navigateur de tenter d'exécuter leur contenu lui-même, laissant pour nous de gérer. Le script (version complète ci-dessous) trouve tous inline-module des éléments de script dans le document, et les transforme en écriture régulière des éléments module le comportement que nous voulons.

Inline scripts ne peuvent pas être directement importés les uns des autres, nous devons donc nous donner les scripts importables Url. Nous générons un blob: d'URL pour chacun d'entre eux, contenant le code et configurer l' src d'attribut à courir à partir de cette URL au lieu de l'exécution de la ligne. L' blob: Url agit comme normale Url du serveur, de sorte qu'ils peuvent être importées à partir d'autres modules. Chaque fois que nous voyons un subséquente inline-module essayez d'importer '#example'example est l'ID d'un inline-module nous avons transformé, nous avons modifier l'importation pour importer à partir de l' blob: URL à la place. Cela permet de maintenir le temps d'exécution et de référence de déduplication que les modules sont censés avoir.

<script type="module" id="dogs" src="blob:https://example.com/9dc17f20-04ab-44cd-906e">
  import {log} from /* #utils */ 'blob:https://example.com/88fd6f1e-fdf4-4920-9a3b';

  log("Exporting dog names.");

  export const names = ["Kayla", "Bentley", "Gilligan"];
</script>

L'exécution du module des éléments de script est toujours reporté jusqu'à ce que le document est analysé, de sorte que nous n'avons pas besoin de s'inquiéter d'essayer à l'appui de la façon que les éléments de script peut modifier le document pendant qu'il est encore être analysée.

export {};

for (const original of document.querySelectorAll('script[type=inline-module]')) {
  const replacement = document.createElement('script');

  // Preserve the ID so the element can be selected for import.
  if (original.id) {
    replacement.id = original.id;
  }

  replacement.type = 'module';

  const transformedSource = original.textContent.replace(
    // Find anything that looks like an import from '#some-id'.
    /(from\s+|import\s+)['"](#[\w\-]+)['"]/g,
    (unmodified, action, selector) => {
      // If we can find a suitable script with that id...
      const refEl = document.querySelector('script[type=module][src]' + selector);
      return refEl ?
        // ..then update the import to use that script's src URL instead.
        `${action}/* ${selector} */ '${refEl.src}'` :
        unmodified;
    });

  // Include the updated code in the src attribute as a blob URL that can be re-imported.
  replacement.src = URL.createObjectURL(
    new Blob([transformedSource], {type: 'application/javascript'}));

  // Insert the updated code inline, for debugging (it will be ignored).
  replacement.textContent = transformedSource;

  original.replaceWith(replacement);
}

Avertissements: cette simple mise en œuvre ne gère pas les éléments de script ajoutés après que le document a été analysée, ou de permettre à des éléments de script pour importer à partir d'autres éléments de script qui se produisent après eux dans le document. Si vous avez à la fois module et inline-module des éléments de script dans un document, leur parent, l'ordre d'exécution peut ne pas être correcte. Le code source de la transformation est effectuée à l'aide d'un brut de regex qui ne gère pas certains cas limites, comme les périodes de l'IDs.

15voto

estus Points 5252

C'est possible avec le service des travailleurs.

Depuis un travailleur des services doit être installé avant d'être en mesure de traiter une page, cela nécessite d'avoir une page distincte pour initialiser un travailleur pour éviter de poulet/oeuf problème ou une page peut être rechargé lorsqu'un travailleur est prêt.

Voici un exemple qui est censé être réalisable dans les navigateurs qui prennent en charge native ES de modules et d' async..await (à savoir Chrome):

index.html

<html>
  <head>
    <script>
(async () => {
  try {
    const swInstalled = await navigator.serviceWorker.getRegistration('./');

    await navigator.serviceWorker.register('sw.js', { scope: './' })

    if (!swInstalled) {
      location.reload();
    }
  } catch (err) {
    console.error('Worker not registered', err);
  }
})();
    </script>
  </head>
  <body>
    World,

    <script type="module" data-name="./example.js">
      export function example() {
        document.body.appendChild(document.createTextNode("hello"));
      };
    </script>

    <script type="module">
      import {example} from './example.js';

      example();
    </script>  
  </body>
</html>

sw.js

self.addEventListener('fetch', e => {
  // parsed pages
  if (/^https:\/\/run.plnkr.co\/\w+\/$/.test(e.request.url)) {
    e.respondWith(parseResponse(e.request));
  // module files
  } else if (cachedModules.has(e.request.url)) {
    const moduleBody = cachedModules.get(e.request.url);
    const response = new Response(moduleBody,
      { headers: new Headers({ 'Content-Type' : 'text/javascript' }) }
    );
    e.respondWith(response);
  } else {
    e.respondWith(fetch(e.request));
  }
});

const cachedModules = new Map();

async function parseResponse(request) {
  const response = await fetch(request);
  if (!response.body)
    return response;

  const html = await response.text(); // HTML response can be modified further
  const moduleRegex = /<script type="module" data-name="([\w./]+)">([\s\S]*?)<\/script>/;
  const moduleScripts = html.match(new RegExp(moduleRegex.source, 'g'))
    .map(moduleScript => moduleScript.match(moduleRegex));

  for (const [, moduleName, moduleBody] of moduleScripts) {
    const moduleUrl = new URL(moduleName, request.url).href;
    cachedModules.set(moduleUrl, moduleBody);
  }
  const parsedResponse = new Response(html, response);
  return parsedResponse;
}

Script de corps sont en train d'être mis en cache (native Cache peut être utilisée aussi bien) et renvoyée pour chaque module demandes.

Préoccupations

  • L'approche est inférieure à l'application construite et chunked avec le regroupement d'outil comme Webpack ou Cumulatif en termes de performance, de flexibilité, de solidité et de prise en charge du navigateur, surtout si le blocage des demandes concurrentes sont la principale préoccupation.

  • Inline scripts augmenter l'utilisation de la bande passante, c'est naturellement évité lorsque les scripts sont chargés une fois et mis en cache par le navigateur.

  • Inline scripts ne sont pas modulaire et en contradiction avec la notion de ES modules (sauf s'ils sont générés à partir de vrais modules côté serveur modèle).

  • Travailleur des services d'initialisation doit être effectuée sur une page séparée pour éviter des requêtes inutiles.

  • La solution est limitée à une seule page et ne prennent pas de <base> compte.

  • Expression régulière est utilisée à des fins de démonstration uniquement. Lorsqu'il est utilisé comme dans l'exemple ci-dessus , il permet l'exécution arbitraire de code JS qui est disponible sur la page. Une expérience éprouvée de la bibliothèque comme parse5 doit être utilisé à la place (il en résulte des performances, et encore, il peut y avoir des questions de sécurité). Ne jamais utiliser des expressions régulières pour analyser DOM.

3voto

bmceldowney Points 2157

Je ne crois pas que c'est possible.

Pour les scripts que vous êtes coincé avec une des méthodes plus traditionnelles de la modularisation du code, comme le namespacing vous démontrée à l'aide d'objets littéraux.

Avec webpack vous pouvez faire code de fractionnement qui vous pouvez utiliser pour prendre une très minime partie du code au chargement de la page, puis progressivement prenez le reste en tant que de besoin. Webpack a aussi l'avantage de vous permettre d'utiliser le module de la syntaxe (en plus d'une tonne d'autres ES201X améliorations) dans la manière la plus environnements qui vient de Chrome Canary.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X