license import * as chai from 'chai' ; import * as parse5 from 'parse5' ; import { Analyzer , FsUrlLoader , FsUrlResolver } from 'polymer-analyzer' ; import { AssignedBundle , BundleManifest } from '../bundle-manifest' ; import { Bundler } from '../bundler' ; import { bundle , HtmlBundler } from '../html-bundler' ; import { parse } from '../parse5-utils' ; import { getFileUrl } from '../url-utils' ; import { heredoc , inMemoryAnalyzer } from './test-utils' ; chai . config . showDiff = true ; const assert = chai . assert ; const stripSpace = ( html ) : string => html . replace ( />\s+/g , '>' ) . replace ( />/g , '>\n' ) . trim ( ) ; suite ( 'HtmlBundler' , ( ) => { test ( 'external script tag inlines without imports/exports' , async ( ) => { const analyzer = inMemoryAnalyzer ( { 'entrypoint.html' : heredoc `
        <script src="module.js" type="module"></script>
      ` , 'module.js' : heredoc `
        console.log('import/export-free code');
      ` , } ) ; const bundler = new Bundler ( { analyzer } ) ; const entrypointUrl = analyzer . resolveUrl ( 'entrypoint.html' ) ! ; const { documents } = await bundler . bundle ( await bundler . generateManifest ( [ entrypointUrl ] ) ) ; const entrypointDoc = documents . getHtmlDoc ( entrypointUrl ) ! ; assert . deepEqual ( entrypointDoc . content , heredoc `
        <script type="module">
        console.log('import/export-free code');
        </script>
      ` ) ; } ) ; test ( 'external script tag inlines an es6 module' , async ( ) => { const root = 'test/html/inline-es6-modules' ; const analyzer = new Analyzer ( { urlResolver : new FsUrlResolver ( root ) , urlLoader : new FsUrlLoader ( root ) , moduleResolution : 'node' , } ) ; const bundler = new Bundler ( { analyzer } ) ; const externalScriptToNodeModuleUrl = analyzer . resolveUrl ( 'external-script-to-node-module.html' ) ! ; const { documents } = await bundler . bundle ( await bundler . generateManifest ( [ externalScriptToNodeModuleUrl ] ) ) ; const externalScriptToNodeModuleDoc = documents . getHtmlDoc ( externalScriptToNodeModuleUrl ) ! ; assert . deepEqual ( externalScriptToNodeModuleDoc . content , heredoc `
      <script type="module">
      const feature = {
        cool: 'thing'
      };
      console.log('imported some-package/main.js');
      </script>
    ` ) ; } ) ; test ( 'inline es6 modules with node module-resolution' , async ( ) => { const root = 'test/html/inline-es6-modules' ; const analyzer = new Analyzer ( { urlResolver : new FsUrlResolver ( root ) , urlLoader : new FsUrlLoader ( root ) , moduleResolution : 'node' , } ) ; const bundler = new Bundler ( { analyzer } ) ; const importUsingNodeModuleResolutionUrl = analyzer . resolveUrl ( 'import-using-node-module-resolution.html' ) ! ; const { documents } = await bundler . bundle ( await bundler . generateManifest ( [ importUsingNodeModuleResolutionUrl ] ) ) ; const importUsingNodeModuleResolutionDoc = documents . getHtmlDoc ( importUsingNodeModuleResolutionUrl ) ! ; assert . deepEqual ( importUsingNodeModuleResolutionDoc . content , heredoc `
      <script type="module">
      const feature = {
        cool: 'thing'
      };
      console.log('imported some-package/main.js');
      console.log(feature);
      </script>
    ` ) ; } ) ; test ( 'inline es6 modules' , async ( ) => { const analyzer = inMemoryAnalyzer ( { 'multiple-inline-modules.html' : `
        <script type="module">
          import {A, B} from './abc.js';
          console.log(A,B);
        </script>

        <script type="module">
          import {B, C} from './abc.js';
          import {Y} from './xyz.js';
          console.log(B,C,Y);
        </script>

        <script type="module">
          import {D,F} from './def.js';
          console.log(D,F);
        </script>
      ` , 'abc.js' : `
        import{upcase} from './upcase.js';
        export const A = upcase('a');
        export const B = upcase('b');
        export const C = upcase('c');
      ` , 'def.js' : `
        import{X, Y, Z} from './xyz.js';
        const D = X + X;
        const E = Y + Y;
        const F = Z + Z;
        export { D, E, F };
      ` , 'omgz.js' : `
        import {upcase} from './upcase.js';
        export const Z = upcase('omgz');
      ` , 'upcase.js' : `
        export function upcase(str) {
          return str.toUpperCase();
        }
      ` , 'xyz.js' : `
        import{upcase} from './upcase.js';
        export const X = upcase('x');
        export const Y = upcase('y');
        export const Z = upcase('z');
      ` , } ) ; const bundler = new Bundler ( { analyzer } ) ; const multipleInlineBundlesUrl = analyzer . resolveUrl ( 'multiple-inline-modules.html' ) ! ; const manifest = await bundler . generateManifest ( [ multipleInlineBundlesUrl ] ) ; const multipleInlineBundlesBundleDocument = await bundle ( bundler , manifest , multipleInlineBundlesUrl ) ; assert . deepEqual ( multipleInlineBundlesBundleDocument . content , heredoc `
      <script type="module">
      import { A, B } from './shared_bundle_1.js';
      console.log(A, B);
      </script>

      <script type="module">
      import { B, C, Y } from './shared_bundle_1.js';
      console.log(B, C, Y);
      </script>

      <script type="module">
      import { X, Y, Z } from './shared_bundle_1.js';
      const D = X + X;
      const E = Y + Y;
      const F = Z + Z;
      console.log(D, F);
      </script>
    ` ) ; } ) ; suite ( 'unit tests of private rewriting methods' , ( ) => { const importDocUrl = getFileUrl ( 'foo/bar/my-element/index.html' ) ; const mainDocUrl = getFileUrl ( 'foo/bar/index.html' ) ; let bundler ; let htmlBundler ; let manifest ; let bundle ; setup ( async ( ) => { bundler = new Bundler ( ) ; await bundler . analyzeContents ( mainDocUrl , '' , true ) ; manifest = await bundler . generateManifest ( [ mainDocUrl ] ) ; bundle = manifest . getBundleForFile ( mainDocUrl ) ! ; htmlBundler = new HtmlBundler ( bundler , bundle , manifest ) ; } ) ; suite ( 'Path rewriting' , async ( ) => { test ( 'Rewrite URLs' , async ( ) => { const css = `
          x-element {
            background-image: url(foo.jpg);
          }
          x-bar {
            background-image: url(data:xxxxx);
          }
          x-quuz {
            background-image: url(\'https://foo.bar/baz.jpg\');
          }
        ` ; const expected = `
          x-element {
            background-image: url("my-element/foo.jpg");
          }
          x-bar {
            background-image: url("data:xxxxx");
          }
          x-quuz {
            background-image: url("https://foo.bar/baz.jpg");
          }
        ` ; const actual = htmlBundler [ '_rewriteCssTextBaseUrl' ] ( css , importDocUrl , mainDocUrl ) ; assert . deepEqual ( actual , expected ) ; } ) ; suite ( 'Resolve Paths' , ( ) => { test ( 'rewrite relative paths, not absolute paths' , ( ) => { const html = `
            <a href="/absolute/path">Absolute Path</a>
            <a href="relative/path">Relative Path</a>
          ` ; const expected = `
            <a href="/absolute/path">Absolute Path</a>
            <a href="my-element/relative/path">Relative Path</a>
          ` ; const ast = parse ( html ) ; htmlBundler [ '_rewriteAstBaseUrl' ] ( ast , importDocUrl , mainDocUrl ) ; const actual = parse5 . serialize ( ast ) ; assert . deepEqual ( stripSpace ( actual ) , stripSpace ( expected ) ) ; } ) ; test ( 'excluding template elements' , ( ) => { const html = `
            <link rel="import" href="../polymer/polymer.html">
            <link rel="stylesheet" href="my-element.css">
            <dom-module id="my-element">
            <template>
            <img src="neato.gif">
            <style>:host { background-image: url(background.svg); }</style>
            <div style="background-image: url(background.svg)"></div>
            </template>
            <script>Polymer({is: "my-element"})</script>
            </dom-module>
            <template is="dom-bind">
            <style>.outside-dom-module { background-image: url(outside-dom-module.png); }</style>
            </template>
            <style>.outside-template { background-image: url(outside-template.png); }</style>` ; const expected = `
            <link rel="import" href="polymer/polymer.html">
            <link rel="stylesheet" href="my-element/my-element.css">
            <dom-module id="my-element" assetpath="my-element/">
            <template>
            <img src="neato.gif">
            <style>:host { background-image: url(background.svg); }</style>
            <div style="background-image: url(background.svg)"></div>
            </template>
            <script>Polymer({is: "my-element"})</script>
            </dom-module>
            <template is="dom-bind">
            <style>.outside-dom-module { background-image: url(outside-dom-module.png); }</style>
            </template>
            <style>.outside-template { background-image: url("my-element/outside-template.png"); }</style>
          ` ; const ast = parse ( html ) ; bundler . rewriteUrlsInTemplates = false ; htmlBundler [ '_rewriteAstBaseUrl' ] ( ast , importDocUrl , mainDocUrl ) ; const actual = parse5 . serialize ( ast ) ; assert . deepEqual ( stripSpace ( actual ) , stripSpace ( expected ) , 'relative' ) ; } ) ; test ( 'inside template elements (rewriteUrlsInTemplates=true)' , ( ) => { const html = `
              <link rel="import" href="../polymer/polymer.html">
              <link rel="stylesheet" href="my-element.css">
              <dom-module id="my-element">
              <template>
              <style>:host { background-image: url(background.svg); }</style>
              <div style="background-image: url(background.svg)"></div>
              </template>
              <script>Polymer({is: "my-element"})</script>
              </dom-module>
              <template is="dom-bind">
              <style>.something { background-image: url(something.png); }</style>
              </template>
              <style>.outside-template { background-image: url(outside-template.png); }</style>
            ` ; const expected = `
              <link rel="import" href="polymer/polymer.html">
              <link rel="stylesheet" href="my-element/my-element.css">
              <dom-module id="my-element" assetpath="my-element/">
              <template>
              <style>:host { background-image: url("my-element/background.svg"); }</style>
              <div style="background-image: url(&quot;my-element/background.svg&quot;)"></div>
              </template>
              <script>Polymer({is: "my-element"})</script>
              </dom-module>
              <template is="dom-bind">
              <style>.something { background-image: url("my-element/something.png"); }</style>
              </template>
              <style>.outside-template { background-image: url("my-element/outside-template.png"); }</style>
            ` ; const ast = parse ( html ) ; bundler . rewriteUrlsInTemplates = true ; htmlBundler [ '_rewriteAstBaseUrl' ] ( ast , importDocUrl , mainDocUrl ) ; const actual = parse5 . serialize ( ast ) ; assert . deepEqual ( stripSpace ( actual ) , stripSpace ( expected ) , 'relative' ) ; } ) ; } ) ; test ( 'Leave Templated URLs' , ( ) => { const base = `
          <a href="{{foo}}"></a>
          <img src="[[bar]]">
        ` ; const ast = parse ( base ) ; htmlBundler [ '_rewriteAstBaseUrl' ] ( ast , importDocUrl , mainDocUrl ) ; const actual = parse5 . serialize ( ast ) ; assert . deepEqual ( stripSpace ( actual ) , stripSpace ( base ) , 'templated urls' ) ; } ) ; } ) ; suite ( 'Document <base> tag emulation' , ( ) => { test ( 'Resolve Paths with <base href> having a trailing /' , ( ) => { const htmlBase = `
          <base href="components/my-element/">
          <link rel="import" href="../polymer/polymer.html">
          <link rel="stylesheet" href="my-element.css">
          <dom-module id="my-element">
          <template>
          <style>:host { background-image: url(background.svg); }</style>
          <img src="bloop.gif">
          </template>
          </dom-module>
          <script>Polymer({is: "my-element"})</script>` ; const expectedBase = `
          <link rel="import" href="components/polymer/polymer.html">
          <link rel="stylesheet" href="components/my-element/my-element.css">
          <dom-module id="my-element" assetpath="components/my-element/">
          <template>
          <style>:host { background-image: url(background.svg); }</style>
          <img src="bloop.gif">
          </template>
          </dom-module>
          <script>Polymer({is: "my-element"})</script>` ; const ast = parse ( htmlBase ) ; htmlBundler [ '_rewriteAstToEmulateBaseTag' ] ( ast , getFileUrl ( 'the/doc/url' ) ) ; const actual = parse5 . serialize ( ast ) ; assert . deepEqual ( stripSpace ( actual ) , stripSpace ( expectedBase ) , 'base' ) ; } ) ; test ( 'Resolve Paths with <base href> with no trailing slash' , ( ) => { const htmlBase = `
          <base href="components/my-element">
          <link rel="import" href="../polymer/polymer.html">
          <link rel="stylesheet" href="my-element.css">
          <dom-module id="my-element">
          <template>
          <style>:host { background-image: url(background.svg); }</style>
          <img src="bloop.gif">
          </template>
          </dom-module>
          <script>Polymer({is: "my-element"})</script>
        ` ; const expectedBase = `
          <link rel="import" href="polymer/polymer.html">
          <link rel="stylesheet" href="components/my-element.css">
          <dom-module id="my-element" assetpath="components/">
          <template>
          <style>:host { background-image: url(background.svg); }</style>
          <img src="bloop.gif">
          </template>
          </dom-module>
          <script>Polymer({is: "my-element"})</script>
        ` ; const ast = parse ( htmlBase ) ; htmlBundler [ '_rewriteAstToEmulateBaseTag' ] ( ast , getFileUrl ( 'the/doc/url' ) ) ; const actual = parse5 . serialize ( ast ) ; assert . deepEqual ( stripSpace ( actual ) , stripSpace ( expectedBase ) , 'base' ) ; } ) ; test ( 'Apply <base target> to all links and forms without target' , ( ) => { const htmlBase = `
          <base target="_blank">
          <a href="foo.html">LINK</a>
          <a href="bar.html" target="leavemealone">OTHERLINK</a>
          <form action="doit"></form>
          <form action="doitagain" target="leavemealone"></form>
          <div>Just a div.  I don't need a target</div>
        ` ; const expectedBase = `
          <a href="foo.html" target="_blank">LINK</a>
          <a href="bar.html" target="leavemealone">OTHERLINK</a>
          <form action="doit" target="_blank"></form>
          <form action="doitagain" target="leavemealone"></form>
          <div>Just a div.  I don't need a target</div>
        ` ; const ast = parse ( htmlBase ) ; htmlBundler [ '_rewriteAstToEmulateBaseTag' ] ( ast , getFileUrl ( 'the/doc/url' ) ) ; const actual = parse5 . serialize ( ast ) ; assert . deepEqual ( stripSpace ( actual ) , stripSpace ( expectedBase ) , 'base target' ) ; } ) ; } ) ; } ) ; } ) ;