Use Babel Transform inside of React code - javascript

I'm trying to convert a string to valid JSX code, and then inject it into my React component.
const babel = require('babel-core')
let result = eval(babel.transform('<p className="greeting">Hello</p>').code)
But am getting hit with a wall of errors, because I'm trying to use Babel in the browser:
ERROR in ./~/babel-core/lib/api/node.js
Module not found: Error: Can't resolve 'fs' in '/Users/ben/Desktop/Work/code/ru-coding-challege/node_modules/babel-core/lib/api'
# ./~/babel-core/lib/api/node.js 72:10-23
# ./~/babel-core/index.js
# ./src/js/containers/app-container.js
# ./src/js/index.js
# multi (webpack)-dev-server/client?http://localhost:3000 ./src/js/index.js
...more similar errors
Yes, I know creating code from a string and injecting it is frowned upon, but D3 creates elements dynamically (i.e. you can't write them declaratively) For example: axes whose values and number of ticks change based on the data. I've successfully changed the D3 axes into JSX with htmltojsx but that returns a String. I need to turn that String into valid JSX components that I can inject into my code.
EDIT: As Michael Lyons states below, I could just use dangerouslySetInnerHTML, but I am trying to avoid this option unless everything else doesn't work. Trying to stay within the React paradigm as much as possible.
Here's how my component's render method would look:
<svg width='100%' height='600'>
<g transform='translate(50, 50)'>
<path d='...' className='path-0'></path>
</g>
{/* Insert JSX elements here. e.g. axes below */}
{axes}
</svg>
And here is my webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin')
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: './src/js/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].bundle.js',
},
devServer: {
inline: true,
contentBase: path.join(__dirname, 'dist'),
port: 3000
},
plugins: [
new CopyWebpackPlugin([
{ from: 'src/html/index.html' }
]),
new webpack.DefinePlugin({
IN_BROWSER: true,
}),
],
module: {
loaders: [
{
test: /\.scss$/,
exclude: /(node_modules)/,
loader: 'style-loader!css-loader!sass-loader'
},
{
test: /\.js$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
}
}

Instead of translating HTML to JSX, then rendering with babel in your code while on the client, you could render that html directly through your React component.
Facebook's DOM element implementation has built in functionality for this use-case, and you are right it is generally frowned upon for security reasons because it opens up vulnerabilities to cross-site scripting.
Facebook has even labeled it "dangerouslySetInnerHTML" to remind devs that this is dangerous.
So if you have HTML in a string format, you can render that in JSX in a manner such as this:
getMarkup() {
return { __html: '<p class="greeting">Hello</p>' }
}
render() {
return <div dangerouslySetInnerHTML={this.getMarkup()} />;
}
This comes straight from the React DOM elements documentation here: Docs
This method should also allow you to bypass having to convert your d3 output to JSX
Edit: Introductory sentence

Related

Trying to save configuration settings to a JSON file on a user's computer from the web browser using a React App that has webpack?

I am working on a social media app for a friend at their request which should allow them to create categories and then write hashtags that correspond to those categories. Afterwards they can just select them as opposed to spending the time typing them all out every single time.
Given the constant updating nature of the project I had set myself for a react web-app and thought I could store the categories and hashtags made in a .JSON file and then any time they added a new category / hashtag, I could just write to that .JSON file.
problem is, using "fs" gets me this error Cannot find module "fs" and the solutions about externals or target Node or other methods invariably results in Uncaught ReferenceError: require is not defined on the .JSX file that makes the reference to fs
I have linked my webpack below. I want to save to a .JSON file on the local computer's file system so I do not have to deal with/pay for some sort of online storage space. I do not want to use the download command because I do not want to open up that dialogue box and would rather have the file be written to a static location in the background. Likewise, I thought a webpage would be a good way to implement the app because when it is built they can just download from github and open the index.html in their preferred browser. If there is another alternative which is less of a headache I am all for listening to suggestions.
var path = require('path')
const mode = process.env.NODE_ENV
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const webpack = require('webpack')
module.exports = {
mode: 'development',
entry: path.join(__dirname,'./index.js'),
output:{
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js',
},
devServer:{
publicPath: '/build/',
port:8080,
hot: true,
},
plugins: [ new MiniCssExtractPlugin(), new webpack.HotModuleReplacementPlugin() ],
module:{
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env','#babel/preset-react']
}
}
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
//'style-loader',
MiniCssExtractPlugin.loader,
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader',
],
}
]
}
}
edited for additional clarity

How to use webpack dynamic imports for submodules?

Let's say I have a single file <google-map> component which has a template which has a <widget> component and a <marker> component.
If I dynamically import my <google-map> component it would look like this:
Vue.component(
'google-map',
() => import('#/components/maps/GoogleMapAsync.vue')
);
Now I know I will not be needing the marker or the widget components outside of the google map component.
So when my map component loads I would also like to import all of the imports that this file has, and even preferably all the way down to the bottom of that tree.
So my map, widget and marker component will all be split off into one chunk.
Is there a way to automate this in Webpack instead of continuously chaining promises or resolving promises from a Promises.all stack?
I guess one way would be to put all of the imports in the same chunk like so:
import(/* webpackChunkName: 'googlemap', '#/components/maps/GoogleMapAsync.vue')
And do this for each component I want to be part of the googlemap chunk, but this is still a lot of manual work for something that I hope can be automated.
Try this babel-plugin-syntax-dynamic-import webpack plugin that let you import using webpack chunks. Also make sure chunks are genenrated.
// Webpack
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js',
chunkFilename: '[name].js'
},
rules: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
plugins: [require('babel-plugin-syntax-dynamic-import')]
}
}]
// Vue code
methods: {
LoadMarker() {
this.componentMarker = () => import (`./path`)
// Vue.js < 2.5 .0 // .then(m=> m.default) ;
}
}
<component :is="componentMarker"></component>

Creating a bookmarklet using webpack, bookmarklet-loader, style and css-loader

I am trying to create a bookmarklet using bookmarklet-loader and the style-loader and css-loader. But I am having trouble importing css into my bookmarklet.
This is what I have
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
bookmarklet: './src/bookmarklets/bookmarklet.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
target: 'web',
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.js$/,
use: [
'bookmarklet-loader'
],
include: path.join(__dirname, './src/bookmarklets')
}
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Development'
})
]
src/bookmarklets/bookmarklet.js:
import './css/style.css';
/* the rest of my bookmarklet code */
src/index.js:
import bookmarklet from './bookmarklets/bookmarklet';
var add = document.createElement("a");
add.href = "javascript:" + bookmarklet;
add.innerHTML = "Click me";
document.body.appendChild(add);
Simply adds the bookmarklet to a link on a blank page, so I can add the link to my browser.
But running webpack produces this error:
SyntaxError: Unexpected token: string (./css/style.css) at [snipped] node_modules/uglify-js/tools/node.js
I tried adding the following to my webpack.config.js:
{
test: /\.js$/,
use: [
'bookmarklet-loader',
'style-loader',
'css-loader'
],
include: path.join(__dirname, './src/bookmarklets')
}
This now compiles fine, but the bookmarklet code contains require statements so when I try and run it in the browser I get an
Uncaught ReferenceError: require is not defined
I have found this and this but have been unable to get this to work.
Edit:
To explain simply the question and solution. I am trying to build a bookmarklet, but the bookmarklet-loader I am using is used for importing bookmarklets into other pieces of code. And this bookmarklet-loader in particular is not setup to handle css and templates required by the bookmarklet. I have switched to using a simple webpack config that produces a compiled javascript file and then this tool to convert that to a bookmarklet.
This is my package.json in case if its of help to anyone:
<snip>
"scripts": {
"build": "webpack && bookmarklet dist/index.js dist/bookmarklet.js && cat dist/bookmarklet.js | xclip -selection clipboard",
}
Now npm run build builds the bookmarklet and copies it to my clipboard so I can update the bookmarklet in the browser.
I've also found this question interesting so here's an answer that would still let you use webpack for bundling your bookmarklet code.
The idea is to use a <script> tag and serve the content as a chunk through webpack:
function addScript(codeURL) {
const scriptElement = document.createElement('script');
scriptElement.setAttribute('src', codeURL);
scriptElement.setAttribute('crossorigin', "anonymous");
document.body.appendChild(scriptElement);
}
With some aditional 'magic', your index.js becomes:
const add = document.createElement("a");
add.href = "javascript:(function(){s=document.createElement('script');s.type='text/javascript';s.src='bookmarklet.bundle.js';document.body.appendChild(s);})()";
add.innerHTML = "Click me";
which is the uglified version of the above function that references your 'bookmarklet.bundle.js' chunk. (this way you don't really need the bookmarklet-loader any more)
The bookmarklet.js source (just a sample):
import './css/style.css';
let elements = require('./someOtherSource');
let list = document.createElement('ul');
for (let i = 0; i < elements.length; ++i) {
let item = document.createElement('li');
item.appendChild(document.createTextNode(elements[i]));
list.appendChild(item);
}
document.body.appendChild(list);
where someOtherSource.js could be as simple as:
module.exports = [ 'a', 'b', 'c'];
and finally, your webpack.config.js becomes:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
index: path.resolve(__dirname, 'src/index.js'),
bookmarklet: path.resolve(__dirname, 'src/bookmarklets/bookmarklet.js'),
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
target: 'web',
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
]
},
{
test: /\.js$/,
use: [
'babel-loader',
],
exclude: /node_modules/,
}
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Bookmarklet',
chunks: [ "index" ],
})
]
};
Again, the advantage I see here is that you get to use your webpack bundling, css/less or whatever other loaders for building your bookmarklet. As reference also see first and second.
The solution you detail in your edit is indeed a perfectly valid way of achieving your objective.
You want to maintain a bookmarklet that depends on injecting styles.
While you can easily inject tags (like <link> and <script>) with a bookmarklet to load external resources into the current page, it does not seem to fit your need because you do not need to make your code available on a server, and trying to link local resources on your file system might not be very reliable.
Therefore you would like the entire code and styles to be contained within the bookmarklet code. You can proceed in 2 steps:
Bundle your JS code with code for inline CSS injection + CSS
Encode and wrap the bundle so that its content can be used as a bookmark.
1. Bundle JS with code for inline CSS injection
This sounds like a perfect job for webpack! Indeed it is meant to bundle your code and inline your styles within the code as well, with style-loader like you did.
You could even push it slightly further by making sure any other asset (image, web font, etc.) that is potentially referred to in your CSS is also inlined, using url-loader with a limit: 0 to always inline those resources.
But as you figured out, you should not use the intermediate artefacts (like for example the output from bookmarklet-loader), since they will likely miss some functionalities (importing style, require).
The webpack output bundle is what you are looking for: a standalone JavaScript code that injects inline styles into the current page and executes your code.
2. Encode and wrap for bookmark
To convert the code into a bookmarklet, you have to encode the content for URI compatibility, and add an extra "javascript:" prefix.
This is the step where you have used the bookmarklet package. But in your case, since all you have is a single JavaScript file that you want to "hard code" into the bookmarklet, the wrapper is dead simple:
'javascript:' + encodeURIComponent('(function(){' + code + '})()')
You can continue using bookmarklet package or make it a very simple node script (but you should move the minification step in a previous step, typically in the webpack configuration).
Actually, it is quite easy to make a webpack plugin for this "bookmarkletify" step:
function AssetToBookmarkletPlugin() {}
AssetToBookmarkletPlugin.prototype.apply = function (compiler) {
compiler.plugin('emit', function (compilation, callback) {
var asset;
// Rework each asset.
for (var assetName in compilation.assets) {
asset = compilation.assets[assetName];
compilation.assets[assetName] = {
source: function () {
// Encode and wrap the original source to make it bookmark-ready.
return 'javascript:' + encodeURIComponent('(function(){' + asset.source() + '})()');
},
size: asset.size
}
}
callback();
});
};
With these additional steps (resources inlining, CSS and JS minification, bookmarkletify assets), your webpack configuration would be:
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
index: './src/index.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
target: 'web',
module: {
rules: [{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loader',
options: {limit: 0} // 0 = always inline resource
}]
}, {
test: /\.css$/,
use: ['style-loader', {
loader: 'css-loader',
options: {minimize: true} // Minify CSS as well
}]
}]
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new AssetToBookmarkletPlugin()
]
};
The content of dist/index.js file is now ready to be copied as a bookmark.
I guess webpack bookmarklet loader is not required to create a bookmarklet itself, as the github repo suggests
"bookmarklet-loader is a webpack loader that will convert any javascript file into a bookmarklet that can be used as a module throughout your application."
Not clear if thats your use case.
looking at the plugin code,
'use strict';
var uglify = require('uglify-js');
module.exports = function(source) {
return 'module.exports = "javascript:' + encodeURIComponent(
'(function(){'+ uglify.minify(source, { fromString: true }).code +'})();'
) + '"';
};
i suspect the issue could be because the only package used here is Uglifyjs which only compiles javascript, and no css loaders in the code.
This plugin expects your code to be pure JS and not any CSS and HTML.
From your code i see that you have configured webpack already to build css and JS, and all this code is offering you is javascript uri pattern wrapped in a function that is URI encoded.
should be pretty simple to DIY after the webpack build output.
hope that helps!

Using CSS in Webpack

I've inherited a web app that uses webpack. In my app, I have a directory called "pub", which looks like this:
./pub
/styles
app.css
/images
brand.png
I have been trying unsuccessfully all morning to use these via webpack. In my webpack.config.js file, I have the following:
const path = require('path');
const projectRoot = path.resolve(__dirname, '../');
module.exports = {
entry: {
app: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
}
]
}
};
Then, in my index.js file, I have the following:
import logoImage from './public/images/brand.png';
require("css!./public/css/app.css");
When I run webpack, I receive an error that says:
BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.
You need to specify 'css-loader' instead of 'css',
see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed
I don't really understand this error. When I look at it, and then I look at my webpack.config.js file, it looks to me like I'm using css-loader. Beyond that though, how do I use a style in my webpage once the require statement is working. I'm just trying to use webpack with a web app and want to import my brand and CSS and I can't figure it out.
You don't need the css! in your require statement
require("css!./public/css/app.css");
You can just use
require("./public/css/app.css");
Because you are testing files with:
{
test: /\.css$/, // <-- here
loader: "style-loader!css-loader"
},
Or without the rule in your webpack config
// No test in rules matched but you tell webpack
// explicitly to use the css loader
require("style-loader!css-loader!./public/css/app.css");
Your hierarchy is pub/styles/app.css but the location you use in your require is public/css/app.css. It looks like you're trying to call your css from the wrong location.
If this doesn't solve your issue, check out this link https://webpack.github.io/docs/stylesheets.html
The first step on that page is to install css-loader and configure it, this might be a good place to start.

React is expected to be globally available

I'm playing with React (#13.3) with babel and webpack.
I have a component that's defined like this:
import BaseComponent from './BaseComponent';
export default class SomeComponent extends BaseComponent {
render() {
return (
<div>
<img src="http://placekitten.com/g/900/600"/>
</div>
);
}
}
But I get the following error:
Uncaught ReferenceError: React is not defined
I understand the error: the JSX bit is compiled into React.createElement(...) but React isn't in the current scope since it's not imported.
My questions is:
What's the clean way to work around this issue? Do I have to somehow expose React globally with webpack?
Solution used:
I followed #salehen-rahman suggestion.
In my webpack.config.js:
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'react-hot!babel?plugins[]=react-require'
}, {
test: /\.css$/,
loader: 'style!css!autoprefixer?browsers=last 2 versions'
}]
},
I also needed to fix my tests, so I added this to the file helper.js:
require('babel-core/register')({
ignore: /node_modules/,
plugins: ['react-require'],
extensions: ['.js']
});
My tests are then launched with the following command:
mocha --require ./test/helper.js 'test/**/*.js'
My questions is : What's the clean way to work around this issue ? Do I have to somehow expose React globally with webpack ?
Add babel-plugin-react-require to your project, and then amend your webpack's Babel config to have settings akin to:
loaders: [
{
test: /.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
stage: 0,
optional: ['runtime'],
plugins: [
'react-require' // <-- THIS IS YOUR AMENDMENT
]
},
}
]
Now, once you've applied the configuration update, you can initialize React components without manually importing React.
React.render(
<div>Yippee! No <code>import React</code>!</div>,
document.body // <-- Only for demonstration! Do not use document.body!
);
Bear in mind though, babel-plugin-react-require transforms your code to automatically include React imports only in the presence of JSX tag in a specific file, for a specific file. For every other file that don't use JSX, but needs React for whatever reason, you will have to manually import React.
If you have react in your node modules directory you can add import React from 'react'; at the top of your file.
You can use Webpack's ProvidePlugin. To use, update the plugins section in your Webpack config to include the following:
plugins: [
new webpack.ProvidePlugin({
'React': 'react'
})
]
This, however, doesn't solve it for the tests..

Categories

Resources