How do you add stylesheets to JSDOM - css

I am currently working on a project that requires me to have computed styles send to the browser via JSDOM. I am currently looking for a way to inject some basic CSS into JSDOM so that it could compute the correct inline style (Yes I know that's bad).
From what I have found out I can use JSDOM Level 2, but from there I can't find any documentation on how to inject the styles.
This is what I have so far;
var document = jsdom.jsdom('<!DOCTYPE html><html><head></head><body id="abody" ></body></html>', jsdom.level(2, 'style'), {
features : {
FetchExternalResources : ['script', 'css'],
QuerySelector : true
}
});
I have been inserting the css into the head tag but to no avail. And I know I could be doing the above code wrong as well.
Any help would be great.

Well, this is going to sounds kinda dumb but this is what I did:
var path = require('path');
var fs = require('fs');
var mainCss = fs.readFileSync(path.normalize(__dirname + "web_main.css"), 'utf8');
var document = jsdom.jsdom('<!DOCTYPE html><html><meta http-equiv="content-type" content="text/html; charset=utf-8"><head></head><body id="abody" ></body></html>', jsdom.level(3, 'index'), {
features : {
FetchExternalResources : ['script', 'css'],
QuerySelector : true
}
});
var window = document.createWindow();
var head = document.getElementsByTagName('head')[0];
style = document.createElement("style");
style.type = 'text/css';
style.innerHTML = mainCss;
head.appendChild(style);
So basically all I changed was moving the level to 3 index, and instead of directly having it in the starting html, I appended it afterwards.
Its pretty simple and I hope it helps someone else out.

solution-preface
Seems Op's Answer_post's solution is:
directly copy and paste the style into the <style>.
If your question is:
How to let Jsdom load (/ why Jsdom doesnt load) external css stylesheet link in an html file? (eg: <link href="main_test.css" rel="stylesheet" />)
Following may help:
solution
Have an html file, say main_test.html.
main_test.html contains <link href="main_test.css" rel="stylesheet" /> -- which is the stylesheet you want to have
#note:
if main_test.html doesnt have it
& you want to add this at run time, maybe you can::
append this line as a string to html file, &
parse the html String (so, not fromFile()) into Jsdom
Load the html file into Jsdom, with the use of resources: 'usable'
dom = await JSDOM.fromFile(pathStr_htmlFile, {
contentType: 'text/html; charset="utf-8"',
resources: 'usable',
});
Make sure you wait until the css stylesheet is fully loaded, before accessing them.
reference
[Can't load external css] https://github.com/jsdom/jsdom/issues/1927

Related

Webpack-React with server-side-rendering: linking to css file in server template with hash name

I'm preparing a starter for react from scratch, here is the code: https://github.com/antondc/react-starter
I managed to set up bundling for client and server, with css modules and less, and now I'm with server side rendering. I'm doing that with a js template:
// src/server/views/index.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>INDEX.EJS</title>
<link rel="stylesheet" type="text/css" href="assets/index.css">
</head>
<body>
<div id="app"></div>
<script src="/assets/bundle.js"></script>
</body>
</html>
As you see, the link to the css file is harcoded there. But in my Webpack configuration I have this file name hashed, because I want to prevent caching from browsers when I update the code on development.
I am wondering how can I link the css file there. Now in the template I have href="assets/index.css, but the css file is in /dist/assets/d47e.css.
It would be great if would be possible to do something like href="assets/*.css, but is not possible, so what is the common approach for a problem like this one?
Thanks!
It depends.
Step 1: Get the current asset name
To get the current name of the generated webpack css/js files, you can use the assets-webpack-plugin. This will (with default config) generate an assets.json file in your output folder with essentially this structure:
{
"bundle_name": {
"asset_kind": "/public/path/to/asset"
}
}
Step 2a: Your html is rendered from a template (pug/jade/what ever)
// in your render code
const assets = require('<webpack-output-folder>/assets.json');
// ...
res.render('template', {
scripts: [{src: `${WEBPACK_PUBLIC_PATH}/${assets.myEntryPointName.js}` }],
links: [{href: `${WEBPACK_PUBLIC_PATH}/${assets.myEntryPointName.css}` rel: 'stylesheet' }],
});
// in your template (example for pug)
// ...
each link in links
link(rel=link.rel href=link.href)
// ...
each script in scripts
script(src=script.src)
// ...
Step 2b: Your html is static
You need to update the html (using a script) with the information from the asset.json file. This script needs to be run after webpack. Something like
const assets = require('<webpack-output-folder>/assets.json');
const fs = require('fs');
const css = /assets\/[a-z0-9]*\.css/i;
const js = /assets\/[a-z0-9]*\.js/i;
fs.readFile('<yourhtml>.html', (err, data) => {
// ... (error handling)
const updatedCss = data.replace(css, assets.myEntryPointName.css);
const updatedJs = updatedCss.replace(js, assets.myEntryPointName.js);
fs.writeFile('<yourhtml>.html', updated, (err) => {
// ... (error handling)
});
});
You can use HTMLWebpackPlugin to generate an HTML file that will have your JS and CSS output inserted.

Node.JS with Express and SASS - Compiled CSS file not applied

I'm new to NodeJS and I'm using Express to serve my pug files/view. Furthermore I'm using "express-sass-middleware" to compile and serve the scss/css files. Everything works very well but unfortunately, the CSS are not applied.
My app.js files looks like:
var express = require('express');
var sassMiddleware = require('express-sass-middleware');
var app = express();
app.set('view engine', 'pug');
app.get('/css/bootstrap.css', sassMiddleware({
file: 'css/bootstrap.scss', // the location of the entry point,
// this can also be a directory
precompile: true, // should it be compiled on server start
// or deferred to the first request
// - defaults to false
}));
app.get('/', function (req, res) {
res.render('index', {
varTitle: 'Hello World'
});
});
app.listen(3000, function() {
console.log('Example app listening on port 3000!');
});
And my simple css file looks like:
// $icon-font-path: /3rdparty/fonts;
// #import 'bootstrap/bootstrap';
// #import './node_modules/bootstrap-sass/assets/stylesheets/bootstrap/variables';
body
{
background-color: green;
font-size: 100px;
}
My index.pug file is:
doctype html
html(lang='en')
head
title= varTitle
link(ref='stylesheet' type='text/css' href='/css/bootstrap.css')
body
h1= varTitle
Now, when I start my webserver using "node app.js", accessing http://localhost:3000, I see "Hello World" but unfortunately the body background isn't green and the text is also not 100px. That means that the css file is not applied. But when I access http://localhost:3000/css/bootstrap.css, I see the valid, css file.
Anyone know what I'm missing here? I'm a bit confused that I see the CSS source when accessing it directly but the browser doesn't apply the css styling. I already tried different browsers without any success. None of them applying the css file.
You have typing error in index.pug file for loading css file. You had mentioned ref whereas it should be rel.
link(rel='stylesheet' type='text/css' href='/css/bootstrap.css')
Happy to help you.
you don't seem to be serving the static files from your nodejs server code. You have to add your css dir in order to allow access from your html code:
app.use('/static', express.static('public'))
Now, you can load the files that are in the public directory from the /static path prefix.
http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
http://localhost:3000/static/js/app.js
http://localhost:3000/static/images/bg.png
http://localhost:3000/static/hello.html

How can I contain the styles of a Chrome Extension content script?

I'm working on a Chrome extension that injects some UI react components into a page.
The UI components come from react-mdl. Using them requires me to include a css file in the top of my project.
Unfortunately, once the css is injected into the page, the entire page's font is changed.
Is there a way to limit the scope of the css used by react-mdl such that it doesn't affect the page into which I'm injecting?
Just posting this for posterity as accepted answer deserves credit, but if anyone finds themselves in a similar predicament, here is a snippet of the code that worked for me:
// my injected code
window.addEventListener('load', () => {
const injectDiv = document.createElement('div')
const shadowRoot = injectDiv.attachShadow({ mode: 'open' })
// note inline use of webpack raw-loader, so that the css
// file gets inserted as raw text, instead of attached to <head>
// as with the webpack style-loader
shadowRoot.innerHTML = // just using template string
`
<style>${require('raw-loader!app/styles/extension-material.css')}</style>
<div id='shadowReactRoot' />
`
document.body.appendChild(injectDiv)
ReactDOM.render(
<App />,
// note you have to start your query in the shadow DOM
// in order to find your root
shadowRoot.querySelector('#shadowReactRoot')
)
})
Then, sure enough:
I think you should use the Shadow DOM API. It is good practice for those cases when you just need to append your UI component to a webpage.
https://developers.google.com/web/fundamentals/getting-started/primers/shadowdom
As mentioned in this other SO post, <link> tag is also supported, so one can simply do as follows:
const injectedDiv = document.createElement('div');
const shadowRoot = injectedDiv.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `\
<link rel="stylesheet" type="text/css" href="${chrome.extension.getURL("bootstrap.min.css")}"></link>\
<link rel="stylesheet" type="text/css" href="${chrome.extension.getURL("whatever.css")}"></link>\
`;
document.body.appendChild(injectedDiv);
Notes:
Using chrome.extension.getURL is required for getting an extension's local resource url, see e.g. in this answer.
The linked .css resources must be declared under the web_accessible_resources property in your manifest.json (otherwise, you'll get this error)

How to use Sass inside a Polymer component

I'm currently using Polymer as my front end development framework. I love SASS.
Now I understand I can create a Sass file and import it like I normally would.
However, I've really gotten into the habit of using style tags within my web components.
Basically the workflow I am looking for is to be able to simply define a script tag within my Web Component maybe add type='sass; to it. Then have grunt go through and compile all of my SASS within those tags before outputting the files to my .tmp directory.
Is something like this achievable with something like Grunt or Gulp? If so what are the best modules to help me achieve this?
My implementation is based on a replacement of a tag inside the Polymer html file. I'm using gulp but could be changed to use simply fs.
The files structure should be as this example:
app-view
|- app-view.html
|- app-view.scss
app-view.html:
<dom-module id="app-view">
<template>
<style>
<!-- inject{scss} -->
</style>
</template>
</dom-module>
app-view.scss:
:host{
margin-top: 50px;
justify-content: center;
display: flex;
}
#container{
font-size: 12px;
h1{
font-size: 20px;
}
}
gulpfile.js:
var gulp = require('gulp');
var nodeSass = require('node-sass');
var path = require('path');
var fs = require('fs');
var map = require('map-stream');
var srcPath = 'src/';
var buildPath = 'build/';
var buildSrcPath = path.join(buildPath, 'target');
gulp.task('processComponents', function () {
return gulp.src([srcPath + '/components/**/*.html'])
.pipe(map(function (file, cb) {
var injectString = '<!-- inject{scss} -->';
// convert file buffer into a string
var contents = file.contents.toString();
if (contents.indexOf(injectString) >= 0) {
//Getting scss
var scssFile = file.path.replace(/\.html$/i, '.scss');
fs.readFile(scssFile, function (err, data) {
if (!err && data) {
nodeSass.render({
data: data.toString(),
includePaths: [path.join(srcPath, 'style/')],
outputStyle: 'compressed'
}, function (err, compiledScss) {
if (!err && compiledScss) {
file.contents = new Buffer(contents.replace(injectString, compiledScss.css.toString()), 'binary');
}
return cb(null, file);
});
}
return cb(null, file);
});
} else {
// continue
return cb(null, file);
}
}))
.pipe(gulp.dest(path.join(buildSrcPath, 'components')));
});
RESULT:
<dom-module id="app-view">
<template>
<style>
:host{margin-top:50px;justify-content:center;display:flex}#container{font-size:12px}#container h1{font-size:20px}
</style>
</template>
</dom-module>
First of all, a million Thanks and gratitude goes to David Vega for showing how it is done! I made some adaptations and optimized the code a little bit.
Here's the github for the file!
https://github.com/superjose/polymer-sass/tree/master
Well, this took me a while. Here it goes!
Polymer unleashed version 1.1. From its website:
Note: Style modules were introduced in Polymer 1.1; they replace the
experimental support for external stylesheets.
Instead, they now support "shared styles".
So this means that we can import .html files with css content. The problem is that we can't do .sass the normal way.
Fortunately here's a simpler solution.
What the following script does is that it gets your .scss files, parse them, and inject them into the shared style .html.
Here is the code. Below it, it's step by step on how to use and setup:
var gulp = require('gulp');
var nodeSass = require('node-sass');
var path = require('path');
var fs = require('fs');
var map = require('map-stream');
var basePath = "app/";
var excludeDir = basePath+"bower_components/";
var ext = "**/*.html";
/**
* We need to specify to nodeSass the include paths for Sass' #import
* command. These are all the paths that it will look for it.
*
* Failing to specify this, will NOT Compile your scss and inject it to
* your .html file.
*
*/
var includePaths = ['app/elements/**/'];
gulp.task('watchSass', function(){
gulp.watch(['app/**/*.scss', '!app/bower_components/**/*.scss'], ["injectSass"]);
});
//This is currently not used. But you can enable by uncommenting
// " //return gulp.src([basePath+ext,...excludeDirs])" above the return.
var excludeDirs = [`!${basePath}/bower_components/${ext}`,`!${basePath}/images/${ext}`]
/**
*
* Enable for advanced use:
*
*
*/
gulp.task('injectSass', function () {
/* Original creator: David Vega. I just modified
* it to take advantage of the Polymer 1.1's shared styles.
*
* This will look all the files that are inside:
* app/elements folder. You can change this to match
* your structure. Note, this gulp script uses convention
* over configuration. This means that if you have a file called
* my-element-styles.html you should have a file called
* my-element-styles.scss
*
* Note #2:
* We use "!" (Exclamation Mark) to exclude gulp from searching these paths.
* What I'm doing here, is that Polymer Starter Kit has inside its app folder
* all the bower dependencies (bower_components). If we don't specify it to
* exclude this path, this will look inside bower_components and will take a long time
* (around 7.4 seconds in my machine) to replace all the files.
*/
//Uncomment if you want to specify multiple exclude directories. Uses ES6 spread operator.
//return gulp.src([basePath+ext,...excludeDirs])
return gulp.src([basePath+ext, '!'+excludeDir+ext])
.pipe(map(function (file, cb) {
//This will match anything between the Start Style and End Style HTML comments.
var startStyle = "<!-- Start Style -->";
var endStyle = "<!-- End Style -->";
//Creates the regEx this ways so I can pass the variables.
var regEx = new RegExp(startStyle+"[\\s\\S]*"+endStyle, "g");
// Converts file buffer into a string
var contents = file.contents.toString();
//Checks if the RegEx exists in the file. If not,
//don't do anything and return.
//Rewrote the if for reduced nesting.
if (!regEx.test(contents)) {
//Return empty. if we return cb(null, file). It will add
//the file that we do not want to the pipeline!!
return cb();
}
/**
* Getting scss
* This will get the .html file that matches the current name
* This means that if you have my-app.component.html
* this will match my-app.component.scss. Replace with .sass if you
* have .sass files instead.
*/
var scssFile = file.path.replace(/\.html$/i, '.scss');
fs.readFile(scssFile, function (err, data) {
//Rewrote the if for reduced nesting.
//If error or there is no Sass, return null.
if (err || !data) {
return cb();
}
nodeSass.render({
data: data.toString(),
includePaths: [path.join('app', 'style/'), ...includePaths],
outputStyle: 'compressed'
}, function (err, compiledScss) {
//Rewrote the if for reduced nesting.
//If error or there is no Sass, return null.
if (err || !compiledScss)
return cb();
/**
* What we are doing here is simple:
* We are re-creating the start and end placeholders
* that we had and inject them back to the .html file
*
* This will allow us to re-inject any changes that we
* do to our .scss or files.
*
*/
var injectSassContent = startStyle +
"<style>" +
compiledScss.css.toString() +
"</style>" +
endStyle;
//This is going to replace everything that was between the <!-- Start Style --> and
// "<!-- End Style -->"
file.contents = new Buffer(contents.replace(regEx, injectSassContent), 'binary');
//This return is necessary, or the modified map will not be modified!
return cb(null,file);
});
});
}))
.pipe(gulp.dest(basePath));
}); //Ends
1) Setup your element:
Suppose you have an element called "hero-tournament":
<dom-module id="hero-tournament">
<template>
<style>
</style>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'hero-tournament',
});
})();
</script>
</dom-module>
And you want to inject your .scss file into it.
Create besides it two new files:
hero-tournament-style.html
hero-tournament-style.scss
Inside the first file, hero-tournament-style.html write the following:
<!-- hero-tournament-style.html -->
<dom-module id="hero-tournament-style">
<template>
<!-- Start Style -->
<style>
</style>
<!-- End Style -->
</template>
</dom-module>
Note the:
<!-- Start Style --> <!-- End Style -->
comments?
These are SUPER important, as all the css will go inside these ones. These are case sensitive, but not position sensitive. Be sure to include them inside your template tags and outside of your style tags.
Then on your hero-tournament-style.scss file, include your sass' css:
(Example)
.blank{
display: none;
}
2) Run Gulp:
gulp watchSass
Bam! You'll see that your "hero-tournament-style.scss" file will be overwritten with your css!!!
<!-- -hero-tournament-style.html -->
<dom-module id="-hero-tournament-style">
<template>
<!-- Start Style -->
<style>.blank{display:none}
</style><!-- End Style -->
</template>
</dom-module>
Now, you can refer that file anywhere!!! Remember your first element, the original one ("hero-tournament.html")? Do the following to it:
<!-- import the module -->
<link rel="import" href="../path-to-my-element/.html">
<dom-module id="hero-tournament">
<template>
<!-- include the style module by name -->
<style include="hero-tournament-styles"></style>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'hero-tournament',
});
})();
</script>
</dom-module>
Some last notes:
Using SASS Imports
Using Sass imports is easy, just need to watch out for the following:
In the gulpfile there is a variable called: "includePaths". It's an array in which nodeSass will look for all the imports. Failing to specify your import in any of the mentioned places, will prevent your file from injecting and compiling. By default, in the script there is a 'app/style' directory which will look for it.
Folder structure
Folder structure is important, and it can be adapted as your liking.
This assumes that your elements are inside an "app" folder brother to your gulpfile (In the same hierarchy):
-gulpfile.js
/app
/element
/hero-tournament
-hero-tournament.html
-hero-tournament-styles.html
-hero-tournament-styles.scss
/maybe-other-folder
If you want to change your folder structure, change the "basePath" variable. Be sure to check for leading "/" so you don't mess up your structure!
How do I run my gulpfile?
It's easy:
Call the "watchSass" method for watching, or "injectSass" for using it once.
gulp watchSass
gulp injectSass
More information in the github page!!!
In Polymer 2.0 it's also possible to just import a stylesheet inside the element's template like that:
<dom-module id="your-module-name">
<template>
<style><!-- you can also add additional styling in here --></style>
<link rel="stylesheet" href="link_to_stylesheet.css">
<!-- your template in here -->
</template>
<script>
//your element class + registration here
</script>
</dom-module>
Inside of the stylesheet you can style your content just like in the style-tag. The styles only affect the element and its content.
If you want to use SASS, Stylus, LESS or anything like that, you just have to use a middleware (HOWTO: Stack Overflow) in Express that renders the SASS-Code into CSS on every request. I prefer this solution over GULP/GRUNT task, because I think it's way easier, because you don't always have to run the Task, because of the Middleware it's compiling automatically whenever it's needed.
I hope that helps you

Dynamic scss variable meteor

I have a scss variable $tint-color that is used in about 100 places.
Once the user logs in, I would like to load a color based on their profile and replace all the usages of $tint-color.
So far I have found two non-ideal solutions:
1) Iterate through all elements and replace the relevant properties.
I am constantly generating new elements -- so this would need to happen repeatedly.
2) Create an override stylesheet, that targets each element.
This will require a lot of duplicate code.
Is there a better / simpler way? I have thought about adding a class to an element in scss, but I am not sure this is possible. Thank you for your help in advance!
What I am doing now, is loading a theme css file after the profile is loaded.
On the server I expose an iron-router route that dynamically replaces any occurrence of the color and returns the theme css.
The issue is that I am not replacing the scss variables, instead I am replacing any occurrence of the color. This is because when the code is executed the .scss files have already been bundled into a .css file on the server.
// return a theme based on the tintColor parameter
this.route('theme', {
where: 'server',
action: function () {
var files = fs.readdirSync('../client');
// find the css file (not the .map file)
var cssFile = _(files).find(function (fileName) {
return fileName.indexOf('.css') > 0 && fileName.indexOf('.map') < 0;
});
var style = fs.readFileSync('../client/' + cssFile, 'utf8');
// remove comments (cannot have them for minification)
style = style.replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/gm, '');
// replace the default tint-color with the dynamic color
style = style.replace(/8cb850/g, this.params.tintColor);
// minify css
if (Settings.isProduction()) {
// from the minifiers package
style = CssTools.minifyCss(style);
}
this.response.writeHead(200, {'Content-Type': 'text/css'});
this.response.end(style);
}
});
Update: I got it to generate with scss variables.
Theme.compile = function (tintColor) {
var dirName = path.dirname(styleFile);
var styles = fs.readFileSync(styleFile, 'utf8');
//replace default theme with dynamic theme
var theme = '$tint-color: #' + tintColor + ';' + '\n';
styles = styles.replace('#import "app/theme.scssimport";', theme);
var options = {
data: styles,
sourceComments: 'map',
includePaths: [dirName] // for #import
};
var css = sass.renderSync(options);
// minify css
if (Settings.isProduction()) {
// remove comments -- cannot have them for minification
css = css.replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/gm, '');
// Use CssTools from the minifiers package
css = CssTools.minifyCss(css);
}
return css;
};
If you do this make sure you add the scss files as assets in the package, example here.
Set a basic $tint-color in your original css.
Then use meteor to send inline CSS with the selected user-tint.
Example:
.tint {
background-color: USER-TINT;
color: USER-TINT;
}
That way you can cache the original css file and save loads of transfer!

Resources