HTMLwebpackPlugin - Is it possible to require partials while in partial? - webpack-dev-server

Earlier I've been using gulp-nunjucks for templating - content extends layout and includes partials. So I've tried to sorta replicate this with html-webpack-plugin
webpack.config.js
const pages = ['home', 'page1'];
const pagesToHTMLPlugin = function(arr) {
return arr.map(function(page) {
return new HtmlWebpackPlugin({
page: page,
template: path.resolve(__dirname, 'src/_layout.html'),
filename: page + '.html',
inject: 'body'
});
});
};
let plugins = pagesToHTMLPlugin(pages).concat(new MiniCssExtractPlugin({ filename: 'main.css' }));
module.exports = {
//...
plugins: plugins
}
at _layout.html i'm successfully requiring partials pages gets rendered with required html files.
//...
<body>
<%= require('html-loader!./partials/header.html') %>
<%= require('html-loader!./pages/' + htmlWebpackPlugin.options.page + '.html') %>
</body>
yet same approach doesn't work if I'm trying to require partial inside already required partial at _layout.html - require('html-loader!./pages/' + htmlWebpackPlugin.options.page + '.html') .
/pages/home.html
<%= require('html-loader!./partials/test-partial.html') %>
Is there any way require html-wepback-plugin partial within another partial? Should I apply some kind of a loader to enable it?

Related

Webpack + React: Passing arbitrary key-value configuration data into JSX

Pretty straightforward thing I want to do: I'm building a React app, using webpack to bundle it. I have some properties that want to pass through, from a configuration JSON file, and be able to refer to those values in my React code.
I figured out a way to do it, though it seems like there should be a more direct way to do it. Looking for suggestions on how to do it more cleanly.
Here's a simplified version of what I'm doing, and it works.
The idea is that I'm threading this value into a hidden element of the HTML, and then passing it into my main React element as props. I'd prefer a way to pass this value directly into React props but have not been able to find a way to do that.
properties.json
{
"myKey": "foo (possibly dynamically generated by a build step)"
}
webpack.config.js
const config = require(__dirname + '/properties.json');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
template: __dirname + '/app/index.html',
filename: 'index.html',
inject: 'body',
metadata: config
});
// ... Rest of normal-looking webpack with babel-loader and react preset
index.html
<html>
<head><!-- normal head contents --></head>
<body>
<!-- One of these per key-value pair in my properties -->
<div id="config-myKey" style="display: none">
<%= htmlWebpackPlugin.options.metadata.myKey %>
</div>
<div id="app"></div>
</body>
</html>
React app (index.js):
const Main = React.createClass({
render: function() {
return(<p>This is the value for myKey: ${this.props.myKey}</p>);
}
});
// Read in the value from the hidden HTML element, and pass it through to the
// React app as props. This part feels like there should be a better way to
// do it.
const myValue = document.getElementById('config-myKey').innerHTML.trim();
ReactDOM.render(
<Main myKey=${myValue}/>,
document.getElementById('app')
);
Turns out DefinePlugin is exactly what I wanted. Thanks #azium.
For completeness, here's exactly how I have it working now. Much cleaner.
properties.json. Note the escaped quotes; those are necessary because I want this to appear as a string literal.
{
"REPLACE_ME_WITH_FOO_VALUE": "\"foo\""
}
webpack.config.js
const config = require(__dirname + '/properties.json');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
template: __dirname + '/app/index.html',
filename: 'index.html',
inject: 'body'
});
const DefinePlugin = new webpack.DefinePlugin(config);
module.exports = {
entry: [ './app/index.js' ],
// ... Rest of normal-looking webpack with babel-loader and react preset
plugins: [HTMLWebpackPluginConfig, DefinePlugin]
});
index.js
const myValue = REPLACE_ME_WITH_FOO_VALUE;
ReactDOM.render(<Main myKey={myValue}/>, document.getElementById('app'));

Multiple modules for multiple entry points in webpack config for reactjs app

In case of multiple entry points, this is the example I got:
module.exports = {
entry: {
user1: path.join(__dirname, './client/app1.js'),
user2: path.join(__dirname, './client/app2.js'),
user3: path.join(__dirname, './client/app3.js')
},
output: {
path: path.join(__dirname, './static/bundle/'),
filename: '[name].js'
},
...
}
But, I am confused about the term entry. Does it mean the entry url? What if user visits to root and then visits another page?
For example, suppose I have one site example.com and two other pages example.com/register and example.com/contact. Now I want to get common files for example.com and load only register module code in example.com/register and contact module code in example.com/contact. The entry point is same, example.com. Will above solution work in this scenario?
But, I am confused about the term entry. Does it mean the entry url?
No. It is the entry for webpack to start bundling. If you have multiple entries in your webpack config, then there will be multiple output bundles. This is why you output uses a [name].js in the filename.
It is up to you to make sure the correct bundle(s) are available to any particular page that is loaded.
Answering question in comment:
If I understand what you are trying to do, I believe you can use CommonsChunkPlugin to construct a common bundle and an entry for each page.
So your webpack config might look something like
module.exports = {
entry: {
index: path.join(__dirname, './src/index.js'),
register: path.join(__dirname, './src/register.js'),
contact: path.join(__dirname, './src/contact.js')
},
output: {
path: path.join(__dirname, './static/bundle/'),
filename: '[name].js'
},
plugins: [
new CommonsChunkPlugin("commons.js")
]
...
}
Then your index page would be something like
<html>
<head></head>
<body>
...
<script src="static/bundle/commons.js"></script>
<script src="static/bundle/index.js"></script>
</body>
</html>
And the register page would be something like
<html>
<head></head>
<body>
...
<script src="static/bundle/commons.js"></script>
<script src="static/bundle/register.js"></script>
</body>
</html>
And the contact page would be something like
<html>
<head></head>
<body>
...
<script src="static/bundle/commons.js"></script>
<script src="static/bundle/contact.js"></script>
</body>
</html>

Standalone React Component with Webpack

I've got a component I'd like to share/reuse in some projects. I'm trying to build/bundle this component so it doesn't take the large setup that react normally does (webpack/babel/npm/ect).
I want to
Include React/ReactDOM from a cdn somewhere on an html page.
Include my component js file (we'll call it standalone.js).
Little bit of initialization code to render this into the dom. No Babel, No Webpack, No JSX.
That's all.
I feel like I've gotton pretty close, though am stuck on item 3. I cannot figure out how render my component to the DOM.
Here's the relevant part of demo html page:
index.html (relevant parts)
<div id="app" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.min.js"></script>
<!--My Component -->
<script src="build/standalone.js" type="text/javascript"></script>
<script>
// I believe I'm doing something wrong here
var myComponent = new MyLibrary.default();
var myStandaloneElement = React.createElement(myComponent, { message: "Testing Standalone Component" });
ReactDOM.render(myStandaloneElement, document.getElementById('app'));
</script>
standalone.jsx
import React, {PropTypes} from 'react';
class Standalone extends React.Component {
render() {
return <p>{this.props.message}</p>;
}
}
Standalone.PropTypes = {
message: PropTypes.string.isRequired
}
export default Standalone;
webpack.config.js (relevant parts)
var config = {
entry: APP_DIR + '/standalone.jsx',
output: {
library: 'MyLibrary',
libraryTarget: 'umd',
path: BUILD_DIR,
filename: 'standalone.js'
},
module: {
loaders: [
{
test: /\.jsx?/,
include: APP_DIR,
loader: 'babel'
}
]
},
externals: {
react: 'React',
"react-dom": 'ReactDOM'
},
}
With trying to render the component with basic html I've tried a bunch of variations of similar things. Looking in my debugger, I can tell the object is something 'close' to a react-type object. I just don't know what to do with it.
Any pointers appreciated
You should not instantiate components with a new, rather they should be instantiated with React.createElement factory. So you just pass reference to the element class/function to createElement, see modified part of yout html:
...
// get reference to my component constructor
var myComponent = MyLibrary.default;
var myStandaloneElement = React.createElement(myComponent, { message: "Testing Standalone Component" });
ReactDOM.render(myStandaloneElement, document.getElementById('app'));
...
On a side note, to simplify debugging while in development (and only in development!) I suggest to use non minified version of react.js and react-dom.js, they are located under node_modules, for instance:
<script src="/node_modules/react/dist/react.js"></script>
<script src="/node_modules/react-dom/dist/react-dom.js"></script>
You may want to consider exposing your React component as a webcomponent, such as with https://www.npmjs.com/package/reactive-elements
<body>
<my-react-component item="{window.someValue}"></my-react-component>
</body>

How to load environment specific configuration with requirejs?

I am making a single page app and using backbone with requirejs. Now I want to include different configurations depending environment variable. I don't know how can I do this and what is best practices.
I have three different config file:
config.js : This keep common configurations.
config.dev.js : This keeps development environment specific configurations
config.production.js : This keeps production specific configurations
I am extending Config model(in config.js) from environment specific model and I requiring the Config model from other modules.
This working fine but downloading dev and production config files both. I want only load config.dev.js or config.production.js not both.
// config.js
define([
'backbone',
'modules/config/config.dev',
'modules/config/config.production'
], function(Backbone, DevConfig, ProdConfig) {
// Select environment model
EnvConfig = (ENV == 'dev') ? DevConfig : ProdConfig;
var ConfigModel = EnvConfig.extend({
// Extending attributes.
defaults: _.extend({
foo : 'bar'
}, EnvConfig.prototype.defaults)
});
return new ConfigModel();
});
In other modules I am using Config as below style:
define([
'backbone',
'modules/config/config'
], function(Backbone, Config) {
var ProfileModel = Backbone.Model.extend({
urlRoot: Config.get('apiRoot') + '/profiles'
...
});
return ProfileModel;
});
How to load one of development or production specific config file, not both?
I'm not sure where the ENV variable comes from, but assuming it's just a global variable you could do something like this:
index.html
<script type="text/javascript">
// adding it here so the example works on its own
window.ENV = 'production'; // or 'dev'
// this will be added to RequireJS configuration
var require = {
paths: {
// 'env-config' will be an alias for actual file name
'env-config': 'config.' + window.ENV
}
};
</script>
<script data-main="scripts/main" src="scripts/requirejs.js"></script>
config.dev.js:
define({
'env': 'development!'
});
config.production.js:
define({
'env': 'production!'
});
main.js:
require(['config', 'env-config'], function(conf, envConfig) {
console.log('The environment is: ', envConfig.env);
});

requirejs with backbone compatibility

The setup below is the only way I can get backbone to ply nice with requirejs - is there a cleaner way? without having to specify the entire path to backbone everytime?
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!doctype html>
<html lang="en">
<head>
<title></title>
<link href='<c:url value="/resources/css/bootstrap.min.css"/>'
rel="stylesheet" type="text/css" />
<script data-main="resources/js/main.js" src="resources/js/lib/require.js"></script>
</head>
<body>
<ul class="nav">
<li >Home</li>
<li>Rentals
<li>Films</li>
</ul>
</body>
</html>
main.js
require.config({
baseUrl : '/sakila/resources/js',
paths : {
jquery : 'lib/jquery-1.8.3.min',
underscore : 'lib/underscore-min',
jqueryui : 'lib/jquery-ui-1.9.2.custom.min'
},
shim : {
'/sakila/resources/js/lib/backbone-min.js' : {
deps : [ 'underscore', 'jquery' ],
exports : 'Backbone'
}
}
});
require([ 'router' ], function(Router) {
Router.initialize();
});
router.js
define(['underscore','jquery','/sakila/resources/js/lib/backbone-min.js'],function(_,$,Backbone){
var AppRouter = Backbone.Router.extend({
routes : {
'home' : 'home',
'films' : 'films',
'rentals' : 'rentals',
'*actions' : 'home', // default action
'':'home'
},
home:function(){
console.log('Routed to home');
},
films:function(){
console.log('Routed to films');
},
rentals:function(){
console.log('Routed to rentals');
},
});
initialize = function() {
var app_router = new AppRouter();
Backbone.history.start();
console.log('history started');
};
return {initialize:initialize};
});
You can create an alias for Backbone in the paths of your requirejs configuration and use that alias in your shims. Also, you don't need to specify the full path for backbone as it respects the baseUrl option in your configuration.
require.config({
baseUrl : '/sakila/resources/js',
paths : {
jquery : 'lib/jquery-1.8.3.min',
underscore : 'lib/underscore-min',
jqueryui : 'lib/jquery-ui-1.9.2.custom.min',
backbone : 'lib/backbone-min'
},
shim : {
backbone : {
deps : [ 'underscore', 'jquery' ],
exports : 'Backbone'
}
}
});
And then use it cleanly elsewhere.
define(['underscore','jquery','backbone'],function(_,$,Backbone){
})
The following post describes how to use Require.js with Backbone.js. It covers 2 different ways of integrating Backbone.js with Require.js that includes shims and AMD compliant backbone.js.
Personally, I would rather use the AMD compliant version of Backbone.js and undescore.js. It makes the configuration a little cleaner and will load the modules in parallel. The folloing blog post describes this option.
Re-Learning Backbone.js – Require.js and AMD

Resources