Closed maosmurf closed 2 years ago
This is intentional, lazy loaded styles are not referenced by the built application automatically and are only intended to be referenced by the developer. As such, a hash prevents the file from being deterministically referenced as the name would change upon every content change.
In addition, since the file is not referenced automatically within the application, the filename can be modified as necessary after the build. So as mentioned, if the project wanted a hash or some other unique identifier attached, it could do so with a post build step.
There is the potential for an additional capability in regards to either allowing template tags in bundleName
or adding an additional option to enable hashing on a per file basis. I'm going to mark this as a feature request.
Thank you for the feedback.
This is intentional
I fully understand the motivation for the recent changes, yet the backwards-incompatible changes caused some headache.
it could do so with a post build step.
Done.
I'm going to mark this as a feature request.
Would be great to have old functionality back using already provided tools (without the need of manual post-build step), so thank you for that 👍
We are running into similar issue with invalidating lazy loaded stylsheets. Would be great if there can be a config setting in angular.json to turn this on or off.
You create lazy-loadable hashed styles with Webpack. See my article about this: https://volosoft.com/blog/Extracting-and-Hashing-Lazy-Loaded-CSS-in-Angular
My Angular CLI version: 8.3.19
angular.json
and remove styles.css
from styles array.style-loader.js
in the src
folder and then replace with the following code:
import styles from './styles.css';
const node = document.createElement('style');
node.textContent = styles;
document.querySelector('head').appendChild(node);
ngOnInit
method in the AppComponent
like below:
ngOnInit() {
setTimeout(() => {
import('src/style-loader');
}, 1500);
}
See Webpack
import()
function. Also you can see Webpack magic comments for chunk naming, etc.
styles.css
to see changes. I added the background color style.Run the ng build --prod
to see the hash
@maosmurf have you found any workarounds?
Our workaround for this was to create a post-build script. Here's some code that anyone is free to use, you may need to modify sourceDir
. It outputs a manifest.json to the root directory so that you are able to reference your hashed filename by filename in your app at runtime.
We also used hasha for the actual hashing.
const hasha = require('hasha')
const fs = require('fs')
let manifest = {}
const sourceDir = './dist'
fs.readdirSync(sourceDir)
.filter((filename) => filename.endsWith('.css'))
.forEach((filename) => {
const hash = hasha.fromFileSync(`${sourceDir}/${filename}`).substring(0, 10) // unique enough for our cache busting purposes
const withoutExtension = filename.slice(0, -4)
const hashedFileName = `${withoutExtension}.${hash}.css`
fs.renameSync(`${sourceDir}/${filename}`, `${sourceDir}/${hashedFileName}`)
manifest[filename] = hashedFileName
})
fs.writeFileSync(
`${sourceDir}/manifest.json`,
JSON.stringify(manifest, null, 4)
)
@mehmet-erim your method seems interesting. Does webpack also minify / prefix styles added this way or we have to provide a css file minified/prefixed beforehand?
@bogacg Obviously I have not checked this. I have provided minified CSS files.
This feature is quite helpful because I want to refer this lazy loaded files within the application code. So, if this lazy loaded styles are hashed it would not be possible to refer these files within application code.
Can't we have any possible workaround if we want to access hashed file styles file from application code?
Case: style files which are loaded (lazily and non lazily) both needs to referenced from service.
I would also like to comment in favour of this feature.
On one hand, we all know how important fast load time is for Web Apps, that's the main motivation of lazy loading modules in Angular.
The problem is that if I reference big UI libraries, like Angular Material or even a specific component, it is a pain to lazy load those styles so I have to include +1Mb of content as part of my main module.
Manually hashing the file is not desired as a simple npm update could change the content and no one might remember to update the hash.
If a post-build task is possible then why don't we include the hashing as default?
is body-enabled so we could reference them inside any component. Why not to specify a component property inside the style object that tells the builder which component will reference the link and then it is added just as it adds
The Angular material themes ideally should not be used within component styles. They are recommended to be placed in a global stylesheet via the project's styles
option or directly in the index.html via a link
element as a project asset. For additional information, please see https://material.angular.io/guide/theming#using-a-pre-built-theme
One of the added benefits of using a global style is that Angular 11 also provides the new inlineCritical
style optimization which will analyze all global styles, inline critical CSS within the HTML and then lazy load the remainder of the global styles.
@clydin I think that's an oversimplification of styles management.
First, most complex app won't use pre-built theme, but custom themes, either by creating their own or by customizing the pre-built ones. In addition you usually also add customizations for components that you don't want to duplicated on every angular component that uses it, like different colors, margins, sizes for tabs, list items, etc.
Where would you put those? If you import the styles in your global stylesheet you are still downloading several Kbs of styles that you don't use to bootstrap the app... if you add customizations to styles.scss to make them global, same thing... The solution here would be to leverage lazy loading styles just the same way we leverage lazy loading modules... perhaps we could even have a concept of module styles... that is similar to the global styles.scss in capabilities... but is only included when the module is loaded.
Currently, my main module bundle is sitting at 1.22Mb after optimizations, and around 30-40% of it are styles imported in the styles.scss file
Styles that affect the entire application such as theming are recommended to be placed in a global stylesheet. With the latest style optimizations enabled it will be loaded in a non-blocking manner in parallel with the initial JavaScript bundles.
Styles that only affect a particular component are recommended to be placed in component specific styles (inline styles
or external styleUrls
). If the component is lazy loaded then these styles will also be lazy loaded. Since the component specific styles are required to be present for the component to successfully render, separately lazy loading the stylesheets for a component will increase the time necessary to initialize and render the component; as well as increase the amount of data that would need to be transferred due to the overhead of the additional file and the code necessary to bootstrap it.
Regarding the size of the main bundle, if the styles are already present in the global styles.scss
, they should not need to be imported into any component styles. There should ideally not be any rule overlap between the global stylesheet and component styles.
@clydin global styles are included as part of the initial bundle for optimization/performance criteria.
What about styles for a Grid? Would you duplicate those styles on each component that uses the grid? Or would you add it to the global styles? If you add it to the global styles, why are we downloading KBs of styles that might never be displayed? (the grids might be part of an admin module?)
With angular material, we are able to import the modules specific to each component individually and when needed... why is then OK to import the styles for the whole library as part of the initial load?
Check a mid-size to complex app and run lighthouse.. it will tell you that a big chunk of your styles are not being used on your home page and suggest removing them to improve load time.
This Angular RFC may be of interest: https://github.com/angular/angular-cli/issues/18730 All of the recommendations have been implemented in the latest stable version of Angular. The developer relations team also put together a video demonstrating some of the features which can be found here: https://youtu.be/yOpy9UMQG-Y
For the grid question, if a grid with a particular setup (style, layout, and/or behavior) is used multiple times within an application, the recommended design pattern is to create an application specific grid component that encapsulates this setup. This component would only be loaded (including its component styles) when needed by a particular route. This also allows centralized updates of the grid appearance/behavior as well as easier enforcement of application style guidelines.
The Angular material components use both global styles (generally for theming) as well as component styles for component specific elements. With the latest versions of Angular, global stylesheets can be downloaded and processed in parallel to the initial JavaScript bundles. JavaScript module downloading, parsing, evaluation, and application bootstrapping will in the majority of cases exceed the time to download and process the global styles. Pruning of unused global styles for the application is also currently a planned feature but has not yet been implemented.
@clydin I think you are still oversimplifying...
You are assuming I'm the one who built the Grid... the grid component might have been built by a 3rd party library... it might actually be part of a huge component library like angular material, ng bootstrap, ag-grid, etc... Those components are very style heavy and usually provide styles as an external file and the recommendation is to either import it as part of styles.scss or add it to the styles array in angular.json.
Going back to the issue I mentioned about... if my home screen contains none of those components... why do I have to include KBs worth of unused styles as part of the initial load?? Just right now I have an enterprise app, with just couple pages and some enterprise grids... only using angular material and ag grid... those 2 styles, after optimization are 200KB... half of the bundle size warnings angular recommends for production apps.
If I want to customize all these components, the only way, AFAIK is to include the style customizations in a global stylesheet (styles.scss)... if I'm building an enterprise app with thousands of customizations... why all these customizations have to be part of the initial load?
Finally, you didn't answer one of my questions... why does Angular material fragmented their components into separate modules and allow us to lazy load them when needed, but we have to load all the library styles as part of the initial load?
The Grid could be a third-party component, first-party or even non-Angular. The encapsulation is still possible and as an application scales in size, this can become increasing beneficial. For example, if the Grid needs to be changed to a different underlying implementation, the change only needs to happen in one place and the entire application is updated. Having a set of shared components that are used throughout an application is a frequently used pattern in Angular applications.
Angular material themes can be customized to only include specific components. Please see the theming guide for additional information: https://material.angular.io/guide/theming#theming-only-certain-components If this alone does not provide a global styles file that meets the project's requirements then the component theme styles can be bifurcated with the less used placed in a relevant lazy route's main component that has view encapsulation turned off.
Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.
Find more details about Angular's feature request process in our documentation.
This issue seems to have gone a bit off-topic, but to refocus on the original request of hashing lazy loaded styles...
We don't really have a feature for "lazy loaded styles" as defined here. You can bundle styles using the CLI and skip injecting them in the index.html
page as is typically done. This pattern is more of an "unreferenced stylesheet" which can be manually loaded onto a page by application code. Angular doesn't do anything special with such a stylesheet after bundling it. (Sidebar: We do have critical CSS inlining which defers some styles to be loaded lazily, however this is a different feature than what's discussed here and is done transparently for developers.)
This distinction is important because appending a hash to the filename makes the reference unstable and means it can't be easily referenced by application code. By contrast, we can add hashes to JS files because the CLI typically controls all the references to them (the index.html
page and dynamic imports of lazy loaded chunks), so we can make sure everything is consistent. Since the CLI doesn't have this same control over bundled styles, we have no hook to allow applications to use a stable reference to a stylesheet with a hash in the filename.
Leaving the hash out of the bundled CSS is the only way to effectively reference that file, so we think it is the right approach here. Including the hash could be useful if Angular had proper support for lazy loading styles, but that's a separate FR. If that's something that you think would be interesting and have a use case for it, then please file that FR.
This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.
We are great fans of lazy-css functionality in
ng build
, it allows us to serve several apps from one code-base, as we add app1.css ... appN.css depending on the request, using angular universal. Given our high-traffic page we use a CDN and therefore rely on changing hashes for changed CSS, as we otherwise would have to manage CSS cache invalidation manually.How can we have hashed lazy CSS back?
See also #11235 and #11704
Bug Report or Feature Request (mark with an
x
)Command (mark with an
x
)Versions
MacOS 10.14 Mojave
Repro steps
my-app/angular.json
->projects/my-apparchitect/build
:Then run:
Example in: https://github.com/maosmurf/ng-demo/tree/lazy-styles
The log given by the failure
Desired functionality
extra.123456678.css
instead ofextra.css
, as it was until recently.While removing the hash from lazy styles helps with lazy includes, it hinders cache invalidation.
Our use-case: We render the app (kurier.at) and have lazy styles. Given we use angular universal, we have no problem checking
/dist
for the current css hash file. After deploying a new version, the new css (with changed hash) is included, yet the old CSS remains valid on CDN (cloudflare) - so there are 2 benefits fron this: 1) no need for invalidating style.css 2) old CSS remains valid for old cached pages.How can we get the old functionality back? Currently, our only option for upgrading to ng6 would be to manually add hashes after
ng build --prod