How to inline React component and related tailwindCSS classes - javascript

I have to send some emails and I simply want to re-use as much code/knowledge as possible (just because), for this I want to render a React component to raw HTML with inline classes.
I have managed to render a React component to static markup via:
const TestMail = () => {
return (
<div>
<h1 className="text-xl font-bold border-b">You have a new Test Email on Productlane</h1>
<p className="border-b">Something something</p>
<a href="https://productlane.io/feedback" className="bg-purple-600">
Open
</a>
</div>
)
}
export function testMailer({ to }: IParams) {
const emailHtml = ReactDOMServer.renderToStaticMarkup(<TestMail />)
const processedHtml = juice(emailHtml, {
webResources: {
// relativeTo: "app/core/styles/index.css",
},
})
return {
async send() {
console.warn("trying to SEND")
console.warn(processedHtml)
},
}
}
This outputs the raw html string without the styles, so I figured I really need to pass the compiled css for the inliner to do its job
<div><h1 class="text-xl font-bold border-b">You have a new Test Email on Productlane</h1><p class="border-b">Something something</p>Open</div>
You can see from the snippet I'm trying to use Juice to inline the styles, however, I can seem to get the classes to be rendered in the html, any idea how to achieve this?

Right I’ve been doing some digging and this is my plan for handling emails.
Transpile down styles sheets to style attribute using Juice, abstract HTML4 tables as react components to allow full email client support.
Support {{ parameters }} leave them in your outputted HTML and pass it through Handlebars to replace them just before sending the email.
Option 1:
Use NextJS static html export to generate HTML files from said React components.
Configure build command to run custom Juice script on outputted files.
Reference the exported files using handlebars to apply the per user context e.g. { name: “David” }. I’m doing this in my sendEmail() function.
Option 2
Use NextJS server endpoint to compile the handlebars template with the per user context. See this article for reference.
You could also replace custom Juice script with this CLI tool or this npm package. Optionally you can even use Inky to abstract away HTML4 tables.
Alternatively if you only want partial email client support NextJS can inline the CSS into the head with this experimental flag discusses here. For full support you will need CSS in style attribute.
I have a lambda function sendEmail(email: string, templateName: string, context: Record<unknown, any>) which has the hubs template files bundle inside it. When the email is sent it then process the context using handlebars compile().

Related

How to add a partial style for a i18n translation?

I want an elegant solution for adding styling like bold, italic, or a color for only a part of a translation.
Example
Let say that I have this i18n translation:
'my-page.confim-customer.popup.warning': 'All customer files must be authorized by the customer himself, please be sure to invite the customer to scan this QR code.'
Now i want that the words "must be authorized by the customer himself" will be in bold and in yellow. (Design requirement)
What can I do?
Solution 1 - Split in i18n
I can simply split the translation to three parts in i18n file:
// i18n/en.js
'my-page.confim-customer.popup.warning.part1': 'All customer files'
'my-page.confim-customer.popup.warning.part2': 'must be authorized by the customer himself,'
'my-page.confim-customer.popup.warning.part3': 'please be sure to invite the customer to scan this QR code.'
and then apply the css i want only on the "part2".
// MyComponent.jsx
const first = i18n.t('my-page.confim-customer.popup.warning.part1');
const second = i18n.t('my-page.confim-customer.popup.warning.part2');
const thrid = i18n.t('my-page.confim-customer.popup.warning.part3');
return (
<>
<div>
{first}
<span className="bold yellow">{second}</span>
{thrid}
</div>
</>
);
Pros
It works
Cons
Polute the i18n files with lots of partial string for each lines.
When change is required it is a hassel to change the view and translation
Solution 2 - Split in code
I keep i18n in the same translation but I add seprator in it (let say $#$):
'my-page.confim-customer.popup.warning': 'All customer files $#$must be authorized by the customer himself,$#$ please be sure to invite the customer to scan this QR code.'
And then in my component i split it:
// MyComponent.jsx
const [first, second, thrid] = i18n.t('my-page.confim-customer.popup.warning').split('$#$')
return (
<>
<div>
{first}
<span className="bold yellow">{second}</span>
{thrid}
</div>
</>
);
Pros
It works
Offload the headache to javascirpt, keep the translation in one i18n key
Cons
Need to process the key before using it
When change is needed (let say we add a new seperator for a section to be italic), we need to alter the way we process the key and change the view to match. not dynamic.
Note
I'm looking for a vanilla solution, as I'm not working with React or a popular framework.
i18next has a special component Trans. There you can use any components inside your string like:
"test" : "All customer files <yelow>must be authorized by the customer himself,</yelow> please be sure to invite the customer to scan this QR code."
...
<Trans
i18nKey='test'
components={{ yelow: <span className="bold yellow"/> }}
/>
The strings split into separate keys are extremely not translator-friendly. It would be hard for translators to provide quality translations since they need to know where each part is used just not to break the UI.
In addition, the translation for some languages likely will have a different word order. The situation is even more complicated in RTL languages.
So, it's definitely recommended to avoid such approaches with string splitting.
As a Vanilla JS solution that can work in your case, I would recommend the Lingui library.
In short, it allows developers to do the following thing by using the #lingui/macro package:
import { Trans } from "#lingui/macro"
<Trans>Read the docs.</Trans>;
Then you can extract all your localizable texts to the .po or .json file by using the Lingui CLI.
Components and HTML tags are replaced with dummy indexed tags (<0></0>) which has several advantages:
both custom React components and built-in HTML tags are supported
change of component props doesn't break the translation
the message is extracted as a whole sentence (this seems to be obvious, but most i18n libs simply split the message into pieces by tags and translate them separately)
Further reading:
Internationalization of JavaScript apps
Working with LinguiJS CLI
#lingui/macro docs

Is it possible to render a PHP frontend into a Vue node?

I have a legacy PHP app which I would like to slowly migrate to Vue. The PHP app renders a bunch of HTML and javascript files in quite a tangled fashion, i.e.
foo.js.php
...
<script src="mysite.com/some_js_file.js" />
...
const a = '<?=$variable_from_php?>';
so in the end, the browser obviously doesn't know how the js files are constructed, but can run them. What I'd like to do is from the outer layer Vue app, request the index page for a certain sub-section of the legacy app, and render that to a Vue node, as a micro-frontend of sorts. When I request each index, it will of course, contain a header with numerous other imports (scripts/styles) that that micro-frontend needs to function. So, two parts to this question: 1) what would be the best (or maybe least terrible) way to do this in Vue. Using v-html? iframe? (please say no iframes) And 2) will there be any showstopper security problems with this approach (since I'm basically saying fetch all the JS in the header and run it). Let me know if this question makes sense. Thanks!
Maybe you need like to : a module php or component as template.php(php server)
export const templateOfAdvanceTemplatePage = `
<div class="content edit-page management">
<md-card class="page-card">
<?php echo "My Component" ?>
</md-card>
</div>
And from node server
import * as url from "url";
var templateOfAdvanceTemplatePage = url.parse("http://www.website.com/template.php");
export default {
template: templateOfAdvanceTemplatePage,
...
}
for more information import vue here, and php as javascript file here
Vue.js can be used in two separate ways: For more complex applications you would use a build process and pre-compile the templates from the source, which are usually Single File Components SFC; *.vue files. The templates would then become render functions and no HTML is ending up in the output assets. There is, however, another way of defining Vue components. You can define them inline with the runtime-only bundle of Vue. For migrations and smaller applications this approach would be advised. You would need to include the compiler. See also the Vue documentation about that topic Vue v2 and Vue v3). If you are importing Vue as a module and are missing the compiler, see here.
If you want to render dynamically generated HTML from PHP as a Vue template, you would need the second approach. Keep in mind that, with this approach, you would always need to have the generated PHP output to be in sync with the Vue components. And you would need to fully trust the HTML, you are generating with PHP, otherwise you will risk injections.
There is, however, still another problem: You need the generated PHP output HTML as a string within JavaScript and it should not be interpreted by the browser (ideally) or removed again from the DOM. So, you need to decide (based on your project) how you want to generate the HTML so that it can be read in as a JavaScript string. Here are some approaches:
Generate the HTML directly into the page. Then, define which element you want to target, get the HTML with .innerHTML and delete the node from HTML (drawback: you will render the HTML twice, might produce short visual glitches).
Fetch the HTML via XHR from a separate page. You will directly have the HTML as a string in the response (see e.g. fetch).
Render <script type="text/x-template" id="static-html-content"></script> around the generated HTML content. Then, you do not need the HTML as string and you can directly use the id as reference (use template: '#static-html-content'). See the documentation of X-Templates in Vue.
Then, you can use the runtime-only version of Vue and define your components. Here is a live example:
const Counter = {
// retrieve and add your template string here
template: `
<div class="counter">
This is a counter: {{ counter }}
<button #click="counter++">Increase Counter</button>
</div>
`,
data: function() {
return {
counter: 0
}
}
};
const App = {
components: { Counter },
template: `
<div class="app">
This is the app component.
<hr />
<counter />
</div>
`
};
new Vue({
el: '#element',
template: '<App />',
components: { App }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="element"></div>
Another approach would be to just render the HTML string within a component with the v-html attribute. The main drawback of this solution is, however, that the content is then not reactive. You cannot change your internal component data and expect the template to react to the changes. Therefore, you are missing out on the main benefits of Vue, but you are not restricted to a template which matches your components internal structure.
A similar question was also posed in the Vue forum: link

Is there any way to access `__filename` in Next.js?

I'm working on a custom i18n module and would love to replace this code (this is a an "about-us" page):
const messages = (await import(`./about-us.${locale}.json`))
.default as Messages;
By
const messages = (
await import(`./${__filename.replace('.tsx', `.${locale}.json`)}`)
).default as Messages;
Unfortunately __filename resolves to /index.js (I guess because of Webpack?) - is there any way to achieve what am I trying to do in my example or this would need to be built-in Next.js directly to work?
Refactor this so consumers don't know about the filesystem
Spoiler: I'm not going to tell you how to access __filename with Next.js; I don't know anything about that.
Here's a pattern that is better than what you propose, and which evades the problem entirely.
First, setup: it sounds like you've got a folder filled with these JSON files. I imagine this:
l10n/
about-us.en-US.json
about-us.fr-FR.json
contact-us.en-US.json
contact-us.fr-FR.json
... <package>.<locale>.json
That file organization is nice, but it's a mistake to make every would-be l10n consumer know about it.
What if you change the naming scheme later? Are you going to hand-edit every file that imports localized text? Why would you treat Future-You so poorly?
If a particular locale file doesn't exist, would you prefer the app crash, or just fall back to some other language?1
It's better to create a function that takes packageName and localeCode as arguments, and returns the desired content. That function then becomes the only part of the app that has to know about filenames, fallback logic, etc.
// l10n/index.js
export default function getLang( packageName, localeCode ) {
let contentPath = `${packageName}.${localeCode}.json`
// TODO: fallback logic
return JSON.parse(FS.readFileSync(contentPath, 'utf8'))
}
It is a complex job to locate and read the desired data while also ensuring that no request ever gets an empty payload and that each text key resolves to a value. Dynamic import + sane filesystem is a good start (:applause:), but that combination is not nearly robust-enough on its own.
At a previous job, we built an entire microservice just to do this one thing. (We also built a separate service for obtaining translations, and a few private npm packages to allow webapps to request and use language packs from our CMS.) You don't have to take it that far, but it hopefully illustrates that the problem space is not tiny.
1 Fallback logic: e.g. en-UK & en-US are usually interchangeable; some clusters of Romance languages might be acceptable in an emergency (Spanish/Portuguese/Brazilian come to mind); also Germanic languages, etc. What works and doesn't depends on the content and context, but no version of fallback will fit into a dynamic import.
You can access __filename in getStaticProps and getServerSideProps if that helps?
I pass __filename to a function that needs it (which has a local API fetch in it), before returning the results to the render.
export async function getStaticProps(context) {
return {
props: {
html: await getData({ page: __filename })
}, // will be passed to the page component as props
};
}
After a long search, the solution was to write a useMessages hook that would be "injected" with the correct strings using a custom Babel plugin.
A Webpack loader didn't seem like the right option as the loader only has access to the content of the file it loads. By using Babel, we have a lot more options to inject code into the final compiled version.

Transpile single React component to use as JS widget

I am working on a fairly complex web app using React.js, and I would like to allow the app's users to use one of the components as an API widget on their sites as well with a script tag (think Google Maps API, Analytics, etc.)
I'm new to React, but I think React takes all of the components, etc. in the app, and bundles them into a single JS file.
Is it possible to bundle only one component into a JS file, then let users place that file in their site, and use it as a widget?
I've tried transpiling the component I want users to use as a widget with reactify, but I can't seem to get it to work.
Any thoughts?
Absolutely, however they will still need to include React as a dependency, or you would have to in your widget.
To use React you don't need to use transpilation, i.e. you don't need reactify. Building a widget for React is like building a widget for jQuery.
// Your widget root component
var SayHelloComponent = React.createClass({
render() {
// You use React.createElement API instead of JSX
return React.createElement('span', null, "Hello " + this.props.name + "!");
}
});
// This is a function so that it can be evaluated after document load
var getWidgetNodes = function () {
return document.querySelectorAll('[data-say-hello]');
};
function initializeWidget(node) {
// get the widget properties from the node attributes
var name = node.getAttribute('data-say-hello');
// create an instance of the widget using the node attributes
// as properties
var element = React.createElement(SayHelloComponent, { name: name });
ReactDOM.render(element, node);
}
getWidgetNodes().forEach(initializeWidget);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<!-- What it would look to include your widget in a page -->
<div data-say-hello="Guzart"></div>
The only reason you would need to transpile your code would be to use JSX and ES6.

True custom attributes (e.g. Microdata) in React

The site I am developing makes use of Microdata (using schema.org). As we are shifting development over to use React to render our views I have hit a blocker where React will only render attributes in the HTML spec however Microdata specifies custom attributes such as itemscope.
As I'm relatively new to React and haven't had chance to fully understand the core just yet, my question is what would be the best way to extend the functionality of react.js to allow for defined custom attributes, e.g., Microdata?
Is there a way of extending the attributes/props parser or is it a job for a mixin which checks all passed props and modifies the DOM element directly?
(Hopefully we'll be able to put together a drop in extension for everyone to provide support for this when a solution is clear.)
You can also use "is" attribute. It will disable the attribute white-list of React and allow every attribute. But you have to write class instead of className and for instead of htmlFor if you use is.
<div is my-custom-attribute="here" class="instead-of-className"></div>
Update React 16 custom attributes are now possible
In react 16 custom attributes are now possible
React 16 custom attributes
It looks like these non-standard properties have been added to React
itemProp: MUST_USE_ATTRIBUTE, // Microdata: http://schema.org/docs/gs.html
itemScope: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE, // Microdata: http://schema.org/docs/gs.html
itemType: MUST_USE_ATTRIBUTE, // Microdata: http://schema.org/docs/gs.html
Note that properties have capital letter in the middle:
<div itemProp="whatever..." itemScope itemType="http://schema.org/Offer">
will generate proper lowercase attributes as result.
You should be able to do it with componentDidMount:
...
componentDidMount: function() {
if (this.props.itemtype) {
this.getDOMNode().setAttribute('itemscope', true)
this.getDOMNode().setAttribute('itemtype', this.props.itemtype)
}
if (this.props.itemprop) {
this.getDOMNode().setAttribute('itemprop', this.props.itemprop)
}
}
...
The whole check for Microdata attributes can be wrapped into a mixin for convenient. The problem with this approach is that it won't work for built-in React component (components created by React.DOM). Update: Looking closer at React.DOM, I come up with this http://plnkr.co/edit/UjXSveVHdj8T3xnyhmKb?p=preview. Basically we wrap the built-in components in a custom component with our mixin. Since your components are built upon React 's built-in DOM components, this would work without you having to include the mixin in the components.
The real solution would be injecting a custom config instead of React's DefaultDOMPropertyConfig, however I can't find a way to do so in a drop-in manner (DOMProperty is hidden by the module system).
For those who's still looking for answers:
https://facebook.github.io/react/docs/tags-and-attributes.html
Example:
<div itemScope itemType="http://schema.org/Article"></div>
So far, the best method I've found is based off of some Amp interop code linked from a comment on react's bug tracker thread on the subject. I modified it slightly to work with a newer version of React (15.5.4) and TypeScript.
For regular ES6, you can just remove the type annotation for attributeName. Using require was needed in TS since DOMProperty isn't exposed in react's index.d.ts, but again import could be used in regular ES6.
// tslint:disable-next-line:no-var-requires
const DOMProperty = require("react-dom/lib/DOMProperty");
if (typeof DOMProperty.properties.zz === "undefined") {
DOMProperty.injection.injectDOMPropertyConfig({
Properties: { zz: DOMProperty.MUST_USE_ATTRIBUTE },
isCustomAttribute: (attributeName: string) => attributeName.startsWith("zz-")
});
}
Now you can use any attribute starting with zz-
<div zz-context="foo" />
Normally it'd be a bad idea to use internal parts of react like this, but I think it is better than any of the other methods. It works the same way as existing open-ended attributes like data- and the JSX is even type safe in TS. I believe the next major version of react is going to do away with the whitelist anyway, so hopefully changes won't be needed before we can remove this shim entirely.

Categories