How to copy files from node_modules with Vite / SvelteKit? - vite

I have a node module dependency for theming content at node_modules/foo-styles.
For most css that I want to pull in, I can simply use an import 'foo-styles/dist/controls/button.css' in the page/component and vite does its magic, shoving the css where it fits best.
For theming however, I need to switch the included css variables file based on active theme in my <svelte:head> , e.g. reference foo-styles/dist/themes/{$theme}/color-variables.css in the markup.
To do so, I assume that vite must copy those files from node_modules/foo-styles to my apps asset directory without import magic, renaming of files etc.
I can only find examples for doing this with webpack or rollup using copy plugins ... is this something that vite can do out of the box? Is there a plugin available? How do you copy assets from node_module dependencies to the bundle?

You can make Vite copy assets and return a URL by adding ?url to the import. With this you then can just dynamically add a link to the <head> with said URL.
E.g.
import themeUrl from 'some-module/css/theme.css?url'
onMount(() => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = themeUrl;
document.head.appendChild(link);
return () => link.remove();
});
If you do not want to manually list all theme imports, you can use Vite's glob import feature. Here the URL has to be written differently as it must start with / or ./:
const styles = import.meta.glob(
'/node_modules/some-module/css/*.css',
{ query: 'url', eager: true },
);
let selectValue = 'some-theme';
$: selectedStyle = `/node_modules/some-module/css/${selectedValue}.css`;
let link = null;
onMount(() => {
link = document.createElement('link');
link.rel = 'stylesheet';
document.head.appendChild(link);
return () => link?.remove();
});
$: if (link) {
const { default: href } = styles[selectedStyle];
link.href = href;
}
You can also use svelte:head, which can be simpler and will add the style reference during SSR, however the order of the stylesheets can be inconsistent as the style may be added before other styles on first load but added after these styles on navigation, so rules may be accidentally overridden.
<script>
const styles = import.meta.glob(
'/node_modules/some-module/css/*.css',
{ query: 'url', eager: true },
);
let selectValue = 'some-theme';
$: href = styles[`/node_modules/some-module/css/${selectedValue}.css`].default;
</script>
<svelte:head>
<link rel="stylesheet" {href} />
</svelte:head>

Related

Convert HTML page to PDF with CSS3 Support

I'm working on a small project whereby I create multiple CVs (or resumes) via an interface I've built in Vue + Laravel, which I can then export to PDF.
I'm having issues though when I export the PDF. Laravel DOMPDF doesn't let me have CSS3 properties inside the PDF, for example flex, or CSS variables. I believe PDFs only support CSS 2.0, but I have seen multiple PDFs being exported that are an exact carbon-copy of the website. For example, resume.io - when you create a CV via their site, they can export it and make it look exactly like the website version.
My question is: does anyone know of a library that I could use that ties into Vue or Laravel that will produce a carbon-copy of the website template into a PDF?
I have tried a few JS libraries that take screenshots of certain elements on the page, then try and fit them together, but it just doesn't work. I basically need a specific element on the page to be selectable and then saved to a PDF. Please see my example below:
As you can see, the white area is the CV preview, so I need that whole section saved to a PDF, minus the right hand side menu and the top-bar. I'm planning on building some really cool templates, but if I can't use modern CSS practices then it's going to be quite hard to make them into a PDF.
At the moment, I've got two views, the CV preview which you can see above, then another view which re-uses partials that are inserted in the PDF template. Obviously though, reusing the partials which have modern CSS applied then makes the PDF break or look broken.
My stack:
Laravel
Vue.js
TailwindCSS
Laravel-DOMPDF
If anyone could advise on the best way to go about this, I'd really appreciate it.
TIA
Since you didn't mentioned the converted page is in Vue or Blade, I'll explain both way.
Here's the Library, which all you need is to design a Blade view, then do something like this
Route::get('/doc', function () {
//
$data = Marketers::all();
// LoadView with $data
$pdf = PDF::loadView('pdf',$data)->setPaper('A4');
// LoadView with Compact
$pdf = PDF::loadView('pdf',compact('data'))->setPaper('A4');
// Then Download it
return $pdf->download('pdf.pdf');
});
Now in Vue you need jsPDF and htmlToCanvas or htmlToIMage
i used HTMLtoImage because i had some character issues for persian language so I'll help you base on HtmlToImage Library.
<template>
// Part you want to Convert to pdf or ...
<div ref="contentz" id="jsPdf" >
// Contents
<div/>
</template>
downloadFull(t) {
let self = this
switch (t) {
case 1:
const doc = new jsPDF("l", "mm", "a4");
htmlToImage.toCanvas(document.getElementById('jsPdf'))
.then(function (canvas) {
var img = canvas.toDataURL("image/jpeg");
var width = doc.internal.pageSize.getWidth();
var height = doc.internal.pageSize.getHeight();
doc.addImage(img, 'JPEG', 0, 0, width, 0);
doc.save('app.pdf');
})
.catch(function (error) {
self.$notifications.failedNotificationOnGetData(self)
});
break;
case 2:
htmlToImage.toJpeg(document.getElementById('jsPdf'), { quality: 1 })
.then(function (dataUrl) {
var link = document.createElement('a');
link.download = 'kalabala.jpeg';
link.href = dataUrl;
link.click();
})
.catch(function (error) {
self.$notifications.failedNotificationOnGetData(self)
});
}
break;
default:
break;
}
}
So here's my function which downloadFull(t) t will be the file type, you might don't need it or you can improve it with simple if/else without switch, first you you will import libraries like :
import jsPDF from 'jspdf';
import htmlToImage from 'html-to-image';
Then set page dimensions for jsPDF, then simply use HtmlToImage to get Canvas then set width, height and image Canvas with variables then simply add image to doc and save it. my functions are same but in first switch case I'll get PDF, in second I'll get JPEG.
If you're trying to do the first way but get download link in Vue page you must do the controller just like i explained at above then in VUE when u do API call you should use BLOB to download the file. Here's the Example :
getPDF(type) {
axios({
url : '/api/api_name/exportPDF',
method: 'POST',
responseType: 'blob'
})
.then(res => {
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'pdf.pdf');
document.body.appendChild(link);
link.click();
})
},
Good Luck.

How to inject Material-UI stylesheets into a jest/react-testing-library test?

It seems that if you don't inject Material-UI stylesheets into a jest/react-testing-library test then jsdom will fail to get the correct styles from your components (e.g. running getComputedStyle(component) will return the incorrect styles for the component).
How you properly setup a jest/react-testing-library test so that the styles are correctly injected into the test? I've already wrapped the components in a theme provider, which works fine.
As a workaround reinserting the whole head (or the element where JSS styles are injected) before assertion seems to apply styles correctly with both getComputedStyle() and react testing library's toHaveStyle():
import React from "react";
import "#testing-library/jest-dom/extend-expect";
import { render } from "#testing-library/react";
test("test my styles", () => {
const { getByTestId } = render(
<div data-testid="wrapper">
<MyButtonStyledWithJSS/>
</div>
);
const button = getByTestId("wrapper").firstChild;
document.head.innerHTML = document.head.innerHTML;
expect(button).toHaveStyle(`border-radius: 4px;`);
});
This will still fail though when you're using dynamic styles, like:
myButton: {
padding: props => props.spacing,
...
}
That's because JSS uses CSSStyleSheet.insertRule method to inject these styles, and it won't appear as a style node in the head. One solution to this issue is to hook into the browser's insertRule method and add incoming rules to the head as style tags. To extract all this into a function:
function mockStyleInjection() {
const defaultInsertRule = window.CSSStyleSheet.prototype.insertRule;
window.CSSStyleSheet.prototype.insertRule = function (rule, index) {
const styleElement = document.createElement("style");
const textNode = document.createTextNode(rule);
styleElement.appendChild(textNode);
document.head.appendChild(styleElement);
return defaultInsertRule.bind(this)(rule, index);
};
// cleanup function, which reinserts the head and cleans up method overwrite
return function applyJSSRules() {
window.CSSStyleSheet.prototype.insertRule = defaultInsertRule;
document.head.innerHTML = document.head.innerHTML;
};
}
Example usage of this function in our previous test:
import React from "react";
import "#testing-library/jest-dom/extend-expect";
import { render } from "#testing-library/react";
test("test my styles", () => {
const applyJSSRules = mockStyleInjection();
const { getByTestId } = render(
<div data-testid="wrapper">
<MyButtonStyledWithJSS spacing="8px"/>
</div>
);
const button = getByTestId("wrapper").firstChild;
applyJSSRules();
expect(button).toHaveStyle("border-radius: 4px;");
expect(button).toHaveStyle("padding: 8px;");
});
This ultimately seems like an issue with JSS and various browser implementations like jsdom and and Blink (at least in Chrome). You can see it in Chrome when trying to modify/enable/disable these style rules (you can't).
The behavior appears to be a result of the JSS library using the CSSOM insertRule API. There's a stylesheet generated in the DOM for the styles we expect in our component, but the tag is empty - it's just used to link the shadow CSS back to the DOM. The styles are never written to the inline stylesheet in the DOM, and as a result, the getComputedStyle method does not return the expected results.
There's an open issue to address this behavior and make development easier.
I switched my custom components to styled-components, which does not have some of these idiosyncrasies.
Material-UI is planning on transitioning soon as well.
You could add this to a custom render function. After rendering, the function pulls the styles out of cssom and puts them into a style tag. Here is an implementation:
let customRender = (ui, options) => {
let renderResult = render(ui, options);
let styleElement = document.createElement("style");
let styleText = "";
for (let styleSheet of document.styleSheets) {
for (let rule of styleSheet.cssRules) {
styleText += rule.cssText + "\n";
}
}
styleElement.textContent = styleText.slice(0, -1);
document.head.appendChild(styleElement);
// remove old style elements
let emptyStyleElements = document.head.querySelectorAll('style[data-jss=""]');
for (let element of emptyStyleElements) {
element.remove();
}
return renderResult;
}
I can't speak specifically to Material-UI stylesheets, but you can inject a stylesheet into rendered component:
import {render} from '#testing-library/react';
import fs from 'fs';
import path from 'path';
const stylesheetFile = fs.reactFileSync(path.resolve(__dirname, '../path-to-stylesheet'), 'utf-8');
const styleTag = document.createElement('style');
styleTag.type = 'text/css';
styleTag.innerHTML = stylesheetFile;
const rendered = render(<MyComponent>);
rendered.append(style);
You don't necessarily have to read from a file, you can use whatever text you want.

chrome extension modify CSS in document_start

The doc here http://archive.is/m7For#selection-5667.63-5669.3 says:
In the case of "document_start", the files are injected after any files from css, but before any other DOM is constructed or any other script is run.
However in a comment here I saw:
I'v realized now. That "CSS" does not refer to <style> and <link rel>
but refers only to CSS injected by manifest
I'm confused about this ..
Is it OK to modify the CSS of the page I inject into in document_start ?
The comment is correct and it is not OK to modify the CSS of the page you inject into in document_start.
When a script is injected with run_at = document_start it can modify only the CSS it itself injected. It does not have access to the DOM including CSS until some point later (probably after the head is created).
However you can modify the CSS of the page before it is shown by using an observer like this:
const convertCSS = () => {
if (!convertCSS.nSheets) convertCSS.nSheets=0;
if (convertCSS.nSheets===window.document.styleSheets.length) return;
convertCSS.nSheets=window.document.styleSheets.length;
for (const styleSheet of window.document.styleSheets) {
const classes = styleSheet.rules || styleSheet.cssRules;
if (!classes) continue;
for (const cssRule of classes) {
if (cssRule.type !== 1 || !cssRule.style) continue;
const selector = cssRule.selectorText, style=cssRule.style;
if (!selector || !style.cssText) continue;
for (let i=0; i<style.length; i++) {
const propertyName=style.item(i), propertyValue=style.getPropertyValue(propertyName);
// YOUR LOGIC HERE ie:
// if (propertyName==='background-color') cssRule.style.setProperty(propertyName, 'yellow', style.getPropertyPriority(propertyName));
}
}
}
}
const observer =new MutationObserver((mutations, observer) => convertCSS());
observer.observe(document, { childList: true, subtree:true });
If you don't need to modify the CSS on new elements once the page is loaded add :
document.addEventListener("DOMContentLoaded", e => observer.disconnect());
Also you probably want "all_frames": true in your manifest.

How to include corresponding css and javascript of the page in assemble layout

I am using assemble to generate from html files with a common layout files. I want to include the corresponding css and javascript file with different pages. So that, for index.html, only index.css and index.js are included, and for about-us.html, only about-us.css and about-us.js are included.
Here's my respository on github https://github.com/xchitox/assemble-gulp-test
If you are already using gulp then use gulp-inject to inject the html files with their respective dependencies based on injection tags.
function injectStartingTag(filepath, starttag) {
var inject = require('gulp-inject');
// Injects the source using relative paths
return inject(gulp.src(filepath, {
read: false
}), {
relative: true,
starttag: '<!-- ' + starttag + ' -->'
});
}
In your index.html:
<!--inject:index:css-->
<!--endinject-->
<!--inject:index:js-->
<!--endinject-->
In your about-us.html:
<!--inject:about-us:css-->
<!--endinject-->
<!--inject:about-us:js-->
<!--endinject-->
Call the function above in any gulp task. You can filter with gulp-if and call the function with the specific starttag. i.e.:
gulp.task('Inject', function(){
var _if = require('gulp-if');
var all_your_files = "**/*.*"; // obvously only add html, js, and css files
return gulp
.src(all_your_files)
.pipe(_if('index.html', injectStartingTag('index.css', 'inject:index:css')))
.pipe(_if('about-us.html', injectStartingTag('about-us.css', 'inject:about-us:css')))
...
...
// you get the idea
});
You can use a helper to generate the link to the assets based on the filename of the current view:
app.helper('filename', function() {
// this.view is the current view being rendered
return this.view.stem; // just get the basename without extension
});
Now you can use this to add the assets path in your layout:
<link rel="stylesheet" href="/assets/css/{{filename}}.css">
<script src="/assets/js/{{filename}}.js"></script>

Look up layouts and include files on multiple paths in express/ejs

I am building a node application based on express using ejs as template engine.
To support different looks for the site I would like to put files in folders named base holding vanilla stuff and an overlay per style/theme/client. I want the system to lookup files in overlay first, and only if not found use what is in base.
For static content like images and css files this works using the static middleware twice, first for the overlay, then for base.
I want to do the same for templates rendered through ejs. I have found:
Multiple View paths on Node.js + Express
And BananaAcids answer provided in that thread almost works for me as long as I call simple ejs views. If I want to use layouts or includes it breaks down for overlaid views because the base directory is now overlay and layouts that are unchanged from base are no longer found.
A simplified example follows.
File base/layouts/root.ejs:
<!DOCTYPE html>
<html lang="en">
<body>
<!-- Main content of pages using this layout goes here -->
<%- body %>
</body>
</html>
File base/index.ejs:
<% layout('layouts/root') -%>
<p>
A page in base using the root layout
</p>
File overlay/index.ejs:
<% layout('layouts/root') -%>
<p>
Totally different page in the overlay.
</p>
Using BananaAcids approach and setting both paths as view-sources express/ejs now correctly locates overlay/index.ejs as the view to render but as I did not also overlay layouts/root it fails because the resulting file overlay/layouts/root.ejs does not exist.
Is there a way of patching my way further down into ejs so that I can help it locate this file in base/layout/root.ejs instead?
Thank you for reading this and any brain cycles you have expended on it.
Here's what I've used to monkey patch Express (4.x) to add layout support:
/*
Usage:
Set a global/default layout with:
app.set('view layout', 'foo');
Set a layout per-render (overrides global layout) with:
res.render('foo', { layout: 'bar' });
Or disable a layout if a global layout is set with:
res.render('foo', { layout: false });
If no layout is provided using either of the above methods,
then the view will be rendered as-is like normal.
Inside your layout, the variable `body` holds the rendered partial/child view.
Installation:
Call `mpResponse();` before doing `require('express');` in your application.
*/
function mpResponse() {
var expressResponse = require('express/lib/response'),
expressResRender = expressResponse.render;
expressResponse.render = function(view, options, fn) {
options = options || {};
var self = this,
req = this.req,
app = req.app,
layout,
cb;
// support callback function as second arg
if (typeof options === 'function')
fn = options, options = {};
// merge res.locals
options._locals = self.locals;
// default callback to respond
fn = fn || function(err, str) {
if (err) return req.next(err);
self.send(str);
};
if (typeof options.layout === 'string')
layout = options.layout;
else if (options.layout !== false
&& typeof app.get('view layout') === 'string')
layout = app.get('view layout');
if (layout) {
cb = function(err, str) {
if (err) return req.next(err);
options.body = str;
expressResRender.call(self, layout, options, fn);
};
} else
cb = fn;
// render
app.render(view, options, cb);
};
}
I patched EJS to support the multiple views folder feature added in Express v.4.10. There is currently a pending pull request you can find here: https://github.com/mde/ejs/pull/120. If you still need this solution for your project you could include my fork into your package.json as a EJS replacement:
{
...
"dependencies": {
"ejs": "git://github.com/MarcelloDiSimone/ejs.git#feature/multi-views"
}
}
..or you plus one the pull request and hope it'll be accepted soon.

Resources