I am trying to switch the href
of a <link />
for theming purposes and the SCSS themes live in a packages folder of my monorepo that are symlinked in node_modules
. I need to be able to compile and reference these.
I came across the following fixed issue: angular/angular-cli#3401 and have been trying to implement something similar:
"styles": [
"styles.scss",
{
"input": "../node_modules/@org/themes/dark.scss",
"output": "dark",
"lazy": true
}
],
My understanding (perhaps incorrect) was that this would compile the dark.scss
file into dist/dark.bundle.css
and that I would be able to load it via http://localhost:4200/dist/dark.bundle.css but it is not working as expected. Am I misunderstanding something or doing this completely wrong?
How do I compile an SCSS file from node_modules
that I can then lazy load in the app? Is there another/better approach I could try instead?
Additional notes:
- Using Angular version
4.2.4
- Using Angular CLI version
1.3.0
- The documentation for this approach
- I am working in a monorepo so
node_modules/@org/themes
is a symlink
- I have tried using
ng serve --preserve-symlinks
option in case the above was the issue. It made no difference
I have looked into the way that the Angular Material docs website approaches this problem and it seems they have a custom build script that compiles the SCSS files to CSS files in the assets
directory before serving the application. I thought the fixed issue above removed the need for this step but perhaps not. Is this the only way it can be done?
SOLVED
Thanks to @Kuncevic. I was missing the --extract-css
flag.
Working config:
"styles": [
"styles.scss",
{
"input": "../node_modules/@org/themes/src/dark.scss",
"output": "themes/dark",
"lazy": true
}
],
And with the following serve script, I can access it via http://localhost:4200/themes/dark.bundle.css:
ng serve --extract-css --preserve-symlinks
By setting "lazy": true
means it won't appear in index.html
but there is no mechanism that will lazy load that bundle for you, check that comment:
The lazy option doesn't actually lazy load anything. It just prevents
it from being executed on application startup.
I agree "lazy": true
is a bit confusing at first.
If you run ng build
you can actually see what's gets outputed in your build and analyze all the files produced by cli.
When you do:
{
"input": "../node_modules/@org/themes/dark.scss",
"output": "dark",
"lazy": true
}
You should be able to access your file directly at http://localhost:4200/dark.bundle.js but it wont appear in index.html
as you set "lazy": true
If you want to get dark.bundle.css
bundle instead of
dark.bundle.js
in dev mode you can use --extract-css
flag.
The reason why cli generating styles in to js
bundle in dev mode is because this way is much quicker. Bud when you do prod build like ng buld --prod
it will output in to .css
by default anyway.
For anyone who want to use global css scipts in .angular-cli.json lazy loaded without hash I wrote following script (e.g. patch-ng-cli.js)
const fs = require('fs');
const stylesFileToPatch = "node_modules/@angular/cli/models/webpack-configs/styles.js";
const regex = /extraPlugins\.push\(.*\}\)\)\;/;
const patchContent = `
// PATCHED CONTENT START
const globalStyles = utils_1.extraEntryParser(appConfig.styles, appRoot, 'styles');
extraPlugins.push(new ExtractTextPlugin({ filename: getPath => {
const generatedFileName = getPath(\`[name]\${hashFormat.extract}.bundle.css\`);
const name = generatedFileName.split(".")[0];
const globalAppStylesConfigEntry = globalStyles.find(path => path.output === name);
if (globalAppStylesConfigEntry && globalAppStylesConfigEntry.lazy){
console.log(\`\${name} will not be hashed due to lazy loading\`);
return \`\${name}.bundle.css\`
}
console.log(generatedFileName);
return generatedFileName;
}}));
// PATCHED CONTENT END
`;
fs.readFile(stylesFileToPatch, (err, data) => {
if (err) { throw err; }
const text = data.toString();
const isAlreadyPatched = !!text.match("PATCHED CONTENT");
if (isAlreadyPatched) return console.warn("-- already patched --", stylesFileToPatch);
console.log('-- Patching ng-cli: ', stylesFileToPatch);
const patchedContent = text.replace(regex, patchContent);
const file = fs.openSync(stylesFileToPatch, 'r+');
fs.writeFile(file, patchedContent, () => console.log("-- Patching -- OK"));
fs.close(file);
});
Then run this script after npm install via npm scripts in package.json
"postinstall": "node ./patch-ng-cli.js",
I know this is an old post, but I have not found a full example just fragments to implement lazy loading in Angular for CSS files or js files. This is a solution with Angular 7.1.4 version and lazy loading of font-awesome 4.7 and bootstrap 4
Install bootstrap and font-awesome
npm install --save font-awesome bootstrap
Edit your angular.json file to make sure that the compiler will generate separated files into the dist folder
"styles": [
"src/styles.css",
{
"input": "./node_modules/font-awesome/css/font-awesome.css",
"lazy": true,
"bundleName": "font-awesome"
},
{
"input": "./node_modules/bootstrap/dist/css/bootstrap.min.css",
"lazy": true,
"bundleName": "bootstrap"
}
],
Parameter explanations:
"input" : "the location where the command from the 1st step will download bootstrap and font-awesome"
"lazy" : "the value of true will make sure that the compiler will not embed the bootstrap.min.css and font-awesome.css files into the compiled files which are send to browser"
"bundleName" : "is the name which you will find into dist folder"
According to this answer you have to add into index.html the following script inside the header tag under the <base href="/">
<script>
window.onload = function () {
function loadScript(scriptUrl) {
const script = document.createElement('script');
script.src = scriptUrl;
document.body.appendChild(script);
}
loadScript('font-awesome.js');
loadScript('bootstrap.js');
}
</script>
NOTE: window.onload = function ()
used to make sure that the page is loaded (this because our files into the dist are in .js format; this is the quick way for Angular to compile)
Run this command, to compile the application
ng build --extract-css=false --prod
NOTE: on build we use --extract-css=false to generate .js files
Testing
- Go into the browser and change it to dev (F12 or right click and inspect element)
- Select Network tab
- Press Ctrl+Shift+R for hard reload
You must be able to see that the bootstrap.js and font-awesome.js are loaded separately like in the photo and also you be able to see for a moment the page without style => this is the moment when the style is loaded well after the DOM is loaded
Since I can't comment the accepted answer, I'll provide an important note to it as a separate one. Please move it there and delete from here if needed. So, the accepted answer is based on separate CSS file. Since Angular 6 you can't use neither --extract-css
or -ec
flags in package.json
for ng serve
, nor extractCss: true
in angular.json
for serve
config section. Though, you can use this approach to make it work. Then you can load your lazy style by using this approach with promise on APP_INITIALIZER
.