Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[AssetMapper] Some comments and questions #52939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
javiereguiluz opened this issue Dec 8, 2023 · 14 comments · Fixed by #53251
Open

[AssetMapper] Some comments and questions #52939

javiereguiluz opened this issue Dec 8, 2023 · 14 comments · Fixed by #53251

Comments

@javiereguiluz
Copy link
Member

javiereguiluz commented Dec 8, 2023

I'm upgrading an app from Webpack Encore to AssetMapper. I have everything working. I love the simplicity that AssetMapper brings in.

symfony-app-assetmapper

Thanks to @weaverryan for creating it and thanks to all the maintainers of the component.

I have some questions:

1. Unconditional shim loading

AssetMapper injects this, which loads a JavaScript file unconditionally (but asynchronously):

<!-- ES Module Shims: Import maps polyfill for modules browsers without import maps support -->
<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js"></script>

I don't like this because:

  • Most browsers support modules already, so this is unnecessary
  • It doesn't look safe enough: is the jspm.io host trustworthy? Who owns it? Also, the link doesn't include an integrity hash, so we don't know what this third-party host will inject in our sites.

We could use this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#feature_detection and some code like this (I haven't tested it, but you get an idea)

if (!HTMLScriptElement.supports?.('importmap')) {
    window.addEventListener('DOMContentLoaded', function () {
        const jsShim = document.createElement('script');
        jsShim.src = 'https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js';

        const firstJsFile = document.getElementsByTagName('script')[0];
        firstJsFile.parentNode.insertBefore(jsShim, firstJsFile);
    };
});
}

2. Full importmap loading

All my pages look like this:

<script type="importmap">
{
    "imports": {
        "app": "/assets/app-1b3462c3ad3db0a53dae6f1629755d2b.js",
        "bootstrap": "/assets/vendor/bootstrap/bootstrap.index-f0935445d9c6022100863214b519a1f2.js",
        "@popperjs/core": "/assets/vendor/@popperjs/core/core.index-ceb5b6c0f9e1d3f6c78ef733facfdcda.js",
        "admin": "/assets/admin-21f507ca8e60a600707f09370efdce36.js",
        // ...
    }
}
</script>

I don't like at all that admin and other JS/CSS that belong to the private part of the application are publicly exposed. This could even leak sensitive information. See next point.

3. CSS/JS Comments are fully exposed

Since CSS/JS files are not processed at all, all their contents are sent verbatim. That means that comments are included too.

I never store passwords, keys, etc. in comments but sometimes I add private information in comments of no-public CSS/JS files (e.g. admin). This could leak private/confidential info.

4. Strange CSS entries in importmap

I see this in the importmap:

<script type="importmap">
{
    "imports": {
        "app": "/assets/app-1b3462c3ad3db0a53dae6f1629755d2b.js",
        "bootstrap/dist/css/bootstrap.min.css": "data:application/javascript,",
        "/assets/styles/app.css": "data:application/javascript,",
        "bootstrap": "/assets/vendor/bootstrap/bootstrap.index-f0935445d9c6022100863214b519a1f2.js",
        "/assets/styles/REDACTED.css": "data:application/javascript,const%20d%3Ddocument%2Cl%3Dd.createElement%28%22link%22%29%3Bl.rel%3D%22stylesheet%22%2Cl.href%3D%22%2Fassets%2Fstyles%2FREDACTED-36dadcef3eec5c0d3a330e5bda46230e.css%22%2C%28d.head%7C%7Cd.getElementsByTagName%28%22head%22%29%5B0%5D%29.appendChild%28l%29",
        // ...
    }
}
</script>

Why do we need the empty "data:application/javascript,"? If they don't contain anything, why add them as entries in the importmap at all?

5. No PurgeCSS

I know that the beauty of AssetMapper is no CSS/JS processing (but we provide optional processing for Sass and Tailwind) but it's sad that we don't have the option to run the PurgeCSS tool to remove all the unneeded CSS.

In this app, we load the entire Bootstrap CSS in a private part of the website, but in the rest of the site we only use 10% or less of Bootstrap.

What we have now:

  • Full Bootstrap CSS unmodified: 274 KB
  • Full Bootstrap CSS minified: 227 KB
  • Full Bootstrap CSS compressed with Brotli: 30 KB
  • Full Bootstrap CSS minified and compressed with Brotli: ~30 KB

So, we're fine because we'll only load 30 KB thanks to Brotli...

... however, with PurgeCSS, we'd load 1 KB or 2 KB.

@weaverryan
Copy link
Member

weaverryan commented Dec 9, 2023

Hi Javier!

Thanks for using AssetMapper and taking the time to give your thoughtful feedback and questions - I really appreciate it :). I've commented, but some are signs of growing from one way or doing things to another. Something feels weird because it's different than before... but it's really fine / not a big deal. I also know that even if that's true, if you ask these questions, other people will too. So let's answer them and make changes / add things to the docs before people need to ask them!

  1. Unconditional shim loading

Hmm, that's an interesting idea. It would actually mean MORE code (in your HTML source) that would need to be downloaded on every request, but it's probably worth it - at least proposing. We should also add the integrity hash, as you mentioned, in any situation.

If we want to get really crazy, we could put the JS you proposed (once tested, of course), into a tiny new shim.js file in symfony/asset-mapper. Then have framework-bundle expose this asset path to AssetMapper. Then the script would be something simple like:

<script src="/assets/framework/es-modules-shim.js"></script>

It would still, ultimately, write a link to the CDN (unless the user has downloaded the file locally - which you can do... so a few details to think about).

  1. Full importmap loading

Yup! We should likely make this clear in the docs, but hiding details is not security anyway. And these paths were actually always available via the public/manifest.json file. They're just more obvious now, so we notice them.

  1. CSS/JS Comments are fully exposed

Same as above. It's an issue for documentation. You should not care :). After documenting it clearly, if you have some super-private site where your public assets contain sensitive comments, you can activate a minification layer during your deploy / in your infrastructure.

  1. Strange CSS entries in importmap

That's a necessary evil. You actually cannot import './some-file.css'. True JavaScript ONLY allows you to import JavaScript files. The importmap entries are how we get around this: when you import a CSS file, it actually imports this tiny JS snippet, which avoids making it fail. We could (for normal, non-async imports) remove CSS imports (i.e. AssetMapper sees the import './file.css', adds a link tag for it, but then removes the line from the final JavaScript so that it's not actually executed). That would remove these entries (except for async CSS imports, but these are rare and that's a tiny price to pay for them). We haven't done that because we are trying, in every possible case, to serve your source files directly with zero changes.

  1. No PurgeCSS

I don't care because I'm using Tailwind, but it's a valid thing to ask about. If someone wants this, they can definitely do it, or even create a bundle:

A) Use https://github.com/postcss/postcss-cli
B) Add & configure purgecss
C) Run postcss and point to your assets/styles/app.css file and output to assets/styles/app.output.css. Then that is what you import from app.js: import './styles/app.output.css';

The bundles like SassBundle & TailwindBundle are just tiny wrappers to download the necessary binary and use some magic to avoid step C (they make AsssetMapper think the content of app.css is actually the content of app.output.css internally so that you can continue importing app.css and not care about things).

@smnandre
Copy link
Member

1/ I'm 100% with you on that one (and i personnally won't use this polyfill), but it's the "less worst" solution ... The DomContentLoaded option would not work in the same way

There is a seamingly better option explained there : https://philipwalton.com/articles/loading-polyfills-only-when-needed/

TL;DR; (copied from this article)

if (browserSupportsAllFeatures()) {
  // Browsers that support all features run `main()` immediately.
  main();
} else {
  // All other browsers loads polyfills and then run `main()`.
  loadScript('/path/to/polyfills.js', main);
}

function main(err) {
  // Initiate all other code paths.
  // If there's an error loading the polyfills, handle that
  // case gracefully and track that the error occurred.
}

@smnandre
Copy link
Member

More generally, and after the recent issues / comments / suggestions on AssetMapper, i believe there is something to clarify between the ImportMap "component" in Symfony LAST stack, and the importmap technology...

I for once would not want to mix JS files with static assets, SVG sprites, LESS source code etc etc... But i fully understand it may be the need / desire of many developpers.

So i guess it's probably also a question of "marketing" and communication on "what can/not be done -- what should/not be done" with this (incredible) component :)

@javiereguiluz
Copy link
Member Author

javiereguiluz commented Dec 27, 2023

Thanks for the answers and comments to my questions. Everything is more clear now.

So, only two issues remain to be fixed:

Thanks

@javiereguiluz
Copy link
Member Author

(I've reopened this issue because there still one pending task.

Note: we can decide to NOT implement the pending task ... but let's make a decision about it. Thanks!

@javiereguiluz javiereguiluz reopened this Dec 28, 2023
@smnandre
Copy link
Member

As i have no access to older computers / browsers, i'm not sure i'll try the last task soon :/

ThomasLandauer added a commit to ThomasLandauer/symfony-docs that referenced this issue Jan 28, 2024
Page: https://symfony.com/doc/6.4/frontend.html

* The info about AssetMapper is taken from symfony/symfony#52939 (Point 3)
* For Encore I merely guessed it ;-)
javiereguiluz added a commit to symfony/symfony-docs that referenced this issue Jan 30, 2024
This PR was squashed before being merged into the 6.4 branch.

Discussion
----------

[Frontend] Adding info about comments

Page: https://symfony.com/doc/6.4/frontend.html

* The info about AssetMapper is taken from symfony/symfony#52939 (Point 3)
* For Encore I merely guessed it ;-)

Commits
-------

076079d [Frontend] Adding info about comments
@ThomasLandauer
Copy link
Contributor

The polyfill you're talking about here is the one that can be disabled via importmap_polyfill, right?

=> Then this issue can be closed.

@smnandre
Copy link
Member

Nope :)

See the uncheck point on @javiereguiluz TODO just before in this issue.

We are experimenting on ux.symfony.com right now, but the question to implement this feature per default on AssetMapper is still very open.

@javiereguiluz
Copy link
Member Author

Let's close this one as fixed because we now load the shim only when needed (see symfony/ux#1381). Thank you all!

@c33s
Copy link

c33s commented Mar 12, 2024

@javiereguiluz @weaverryan i am not sure if this change doesn't make things worse.

i was really shocked as i noticed that a file was loaded from a CDN as i used the asset mapper the first time. i have a strict no-CDN policy. it was a real wtf moment. first i thought i got hacked somehow. i headed to the docs where i found no explanation searching for polyfill (yes sometimes you overread a big green box xD). a colleague pointed me to the right direction php bin/console importmap:require es-module-shims

  • according the GDPR ips are personal data.
  • in austria we had a massive lawsuite about sideloading google fonts (ip is transfered to google without consent) which was took down because so many people where hit by it but in the strict sense the exposing of an ip address to a 3rd-party is still a problem.
  • many pages switched to serving fonts from their own domain or added it to the "consent banner" where the external files are loaded AFTER the users gave their consent.
  • if the change from [Site] Load importmap polyfill conditionnaly ux#1381 hits the flex receipe of assetmapper the sideloading is kind of hidden. on my local machine where i have an uptodate browser i won't see that something is sideloaded but if a user comes to the site with an old browser, suddenly something is sideloaded and the developers may not even know about (again ONLY if this change goes into the default receipe).

i would argue for a strict opt-in solution. NEVER sideload something except the developer explicitly requested this behavior. so i think we should always use a local polyfill file per default if importmap_polyfill is true

i am not sure which way you will go after the merge of PR symfony/ux#1381 will this solution find its way into a flex recipe where suddenly something is sideloaded by default? we should keep this issue open or maybe even create a new one to be quite explicit and transparent about this.

@smnandre
Copy link
Member

Let's close this one as fixed because we now load the shim only when needed (see symfony/ux#1381). Thank you all!

We do on ux.symfony.com, but this is not the default behaviour of the AssetMapper in the framework

I do believe we should make this polyfill optional, as importmap is available on 92% browser according to caniuse.com, but depending on the region/country it can be way more than that

Example: Caniuse evaluate IE market share at 0.45%, but in France it's now around 0.10%...

@smnandre
Copy link
Member

if the change from symfony/ux#1381 hits the flex receipe of assetmapper the sideloading is kind of hidden.

@c33s : That PR concerned the website of Symfony UX, never was intended to be ported back "like that" on symfony/symfony. We wanted to see if not adding the polyfill for everyone was going to create problems. And we tested some asynchronous loading. But i insist, that was a live test on a website, not a PR for the asset-mapper component :)

i would argue for a strict opt-in solution. NEVER sideload something except the developer explicitly requested this behavior. so i think we should always use a local polyfill file per default if importmap_polyfill is true

I 100% agree with you

@smnandre
Copy link
Member

@javiereguiluz could you reopen please ? (as i understand it you closed because you did think the ux PR was ported on symfony framework, sorry if i'm wrong)

@javiereguiluz
Copy link
Member Author

Let's reopen then. Sorry!

@javiereguiluz javiereguiluz reopened this Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants