How do I use ejs templates/partials with webpack? - node.js

I'm trying to create a full stack app using Express and ejs, and using webpack to bundle the client side assets. When I set it up without ejs it all works perfectly. The problem has come when I've tried to change the index.html to an index.ejs that uses partials for header, footer etc. Ultimately this will be a multipage app which is why I'm using the ejs templates.
I've tried using ejs-loader (https://www.npmjs.com/package/ejs-loader), ejs-compiled-loader (https://github.com/bazilio91/ejs-compiled-loader) and ejs-html-loader (https://www.npmjs.com/package/ejs-html-loader) without any joy.
Any help would be appreciated. I'm sure it's really straightforward but for the life of me I can't work out how to link up ejs to webpack and get the templates to work.
I've included the code that works with template.html below. I need to know how to change that to use index.ejs?
My file structure looks like this:
--dist (output from webpack)
--src (containing index.ejs)
----partials (containing partials for index.ejs)
app.js
webpack.common.js
webpack.dev.js
webpack.prod.js
package.json
etc.
Index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<% include partials/head %>
</head>
<body>
<main>
<% include partials/main %>
</main>
<% include partials/footer %>
</body>
</html>
And my config files look as follows (I've only included the two that merge to make the build congfig):
webpack.common.js
const path = require("path")
module.exports = {
entry: {
main: "./src/index.js",
vendor: "./src/vendor.js"
},
module: {
rules: [
{
test: /\.html$/,
use: ["html-loader"]
},
{
test: /\.(svg|png|jpg|gif)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath: "imgs"
}
}
}
]
},
}
webpack.prod.js
const path = require("path")
const common = require("./webpack.common")
const merge = require("webpack-merge")
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, {
mode: "production",
output: {
filename: "[name].[contentHash].js",
path: path.resolve(__dirname, "dist")
},
optimization: {
minimizer: [
new OptimizeCssAssetsPlugin(),
new TerserPlugin(),
new HtmlWebpackPlugin({
template: "./src/template.html",
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true,
removeComments: true,
}
})]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contentHash].css",
}),
new CleanWebpackPlugin(),
],
module: {
rules: [{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}]
}
});
This is my first post, if I've not included anything crucial please let me know!
Thanks!

Related

Entrypoint undefined = index.html using HtmlWebpackPlugin

I'm using Webpack 4 and I'm creating the config file, when trying to use the HtmlWebpackPlugin it got this on the console: Entrypoint undefined = index.html, it opens the browser and the HTML does appear but I'm getting this weird message on the console, how to solve this?
That is how my config file looks like:
'use strict'
const webpack = require('webpack')
const { join, resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development', // dev
devtool: 'cheap-module-eval-source-map', // dev
entry: join(__dirname, 'src', 'index.js'),
output: {
filename: 'bundle.js',
path: resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
devServer: {
contentBase: resolve(__dirname, 'build')
},
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({
template: join(__dirname, 'public', 'index.html')
}),
new webpack.HotModuleReplacementPlugin(), // dev
new webpack.NoEmitOnErrorsPlugin() // dev
]
}
Try this; you might be making wrong template path :
new HtmlWebpackPlugin({
template: resolve(__dirname, 'src/public', 'index.html'),
filename: './index.html'
}),
If public is in src folder this should work It's my assumption.
Let me know if the issue still persists.
According to the creators of HtmlWebpackPlugin it's just a meaningless log message and an ignorable cosmetic issue. See this comment on the issue.
It seems like a problem with the extension of the template firing an unwanted loader. If the extension of the template is changed to any other the plugin will work.
If you're using the default webpack template system (EJS, as of webpack 4) it makes sense to use ejs because the template isn't valid html anymore:
new HtmlWebpackPlugin({
// it works without 'path.resolve()'. No need for 'filename', defaults to 'index.html'
template: "./public/index.ejs",
}),
webpack considers by default that the template is EJS and will automatically process it with the proper loader. If you use any other template system you will have to add the corresponding loader. More info on official documentation.
plugins: [
...
new HtmlWebPackPlugin({
title: '...',
template: path.resolve(folderSrc, 'index.html'),
filename: 'index.html',
hash: true
})
]
Error got fixed in mine by adding this in webpack.config.js :
stats: { children: false }, //to fix the error-Entrypoint undefined=index.html
plugins:[
new HtmlWebpackPlugin({
template: './index.html'
})
]

CSS Loader seems to not to export anything

I have a nodejs + typescript + ReactJS + webpack + css loader application.
I got to get imports to CSS modules, i.e. my statements
import * as styleILove from './css/mycoolCSS.css';
Bundles properly and the output seems valid. Otherwise the application works fine.
The problem is, if I do console.log (styleILove) the object exists but there is nothing inside the object. According to CSS loader documentation I should be able to issue console.log(styleILove.myClassName) but there is nothing. In the console of the browser it does not exist and VS code highlight also complains.
Any ideas why that is failing?
My webpack config:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const config = {
entry: "./src/index.tsx",
resolve: {
extensions: ['.tsx', '.js', '.css']
},
output: {
filename: "bundle.js",
},
plugins: [
new HtmlWebpackPlugin({
title: 'asdfasdf',
}),
],
module: {
rules: [
{
test: /.tsx$/,
loader: "ts-loader" ,
},
{
test: /.css$/,
use: [ 'style-loader', 'css-loader' ],
}
]
}
}
module.exports = config;
My css:
.myClassName {
box-shadow: 1cm;
}
I usually do it like this:
const css = require("./css/mycoolCSS.css");
export const TestComponent: React.SFC = (): JSX.Element => (<div className={css["myClassName"]}/>);

Babel import css syntax error

I want to be able to use import in my react application for not only js/jsx files but also for css files. From what I've read, the best way to do that is to use the extract-text-webpack-plugin which will take your imported css files and bundle them together.
I've set it up so that its generating my bundled css file, but for some reason every time I load my page I get a syntax error:
SyntaxError: MyWebpage/views/global.css: Unexpected token, expected ; (1:5)
> 1 | body {
| ^
2 | margin: 0;
3 | }
My setup looks like this:
webpack.config.js
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const config = {
entry: ['babel-polyfill', './views/Index.jsx'],
output: {
path: path.resolve(__dirname, 'public'),
filename: 'bundle.js',
publicPath: '/public'
},
module: {
rules: [
{ test: /\.(jsx|js)$/, exclude: /node_modules/ , use: 'babel-loader' },
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin("styles.css"),
]
};
module.exports = config;
The entry point ./views/Index.js is where I'm importing my css file:
Index.js
import React from 'react';
import Layout from './Layout.jsx';
import PageContent from './PageContent.jsx';
import './global.css';
class Index extends React.Component {
render() {
return (
<Layout title={this.props.title}>
<PageContent />
</Layout>
);
}
}
export default Index;
Inside the imported ./Layout.jsx file I'm using a <link> to include the bundled css file in my page:
Layout.jsx
import React from 'react';
class Layout extends React.Component {
render() {
return (
<html>
<head>
<title>{this.props.title}</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="root">
{this.props.children}
</div>
<script type="text/javascript" src="./bundle.js"></script>
</body>
</html>
);
}
}
export default Layout;
I'm pretty confused because it seems like my app is building fine, but when I try to access my webpage I keep getting a syntax error.
Can anyone please help me understand what I'm doing wrong?
It seems problem with loaders below is example of webpack.config.js file working for jsx and css loaders :
module.exports = {
entry: './app/index.js',
output: {
path: __dirname,
filename: 'dist/bundle.js'
},
devServer: {
inline: true,
port: 3000
},
module: {
loaders: [{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react', 'react-hmre']
}
},
{
test: /\.scss$/,
loaders: [ 'style', 'css', 'sass' ]
}]
}
};
it seems like babel or webpack is not loading the loaders.
This variant helps me with it (just include into webpack.config.js):
require.extensions['.css'] = () => {
return;
};
More here... [link]

bundle.js not found. React, Node, Webpack

I have generated bundle.js file but it is not available when I run server.
Same thing is for style.css.
Probably error is small but I can't find it.
build
NODE_ENV='production' webpack -p
start
NODE_ENV=production NODE_PATH=./src node server.js
server.js
require('babel-core/register');
['.css', '.less', '.sass', '.ttf', '.woff', '.woff2'].forEach((ext) => require.extensions[ext] = () => {});
require('babel-polyfill');
require('server.js');
webpack.config.js
global.Promise = require('bluebird');
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const publicPath = './public';
const cssName = 'styles.css';
const jsName = 'bundle.js';
const plugins = [
new webpack.DefinePlugin({
'process.env': {
BROWSER: JSON.stringify(true),
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
}
}),
new ExtractTextPlugin(cssName)
];
if (process.env.NODE_ENV === 'production') {
plugins.push(
new CleanWebpackPlugin([ './public/' ], {
root: __dirname,
verbose: true,
dry: false
})
);
plugins.push(new webpack.optimize.DedupePlugin());
plugins.push(new webpack.optimize.OccurenceOrderPlugin());
}
module.exports = {
entry: ['babel-polyfill', './src/client.js'],
debug: process.env.NODE_ENV !== 'production',
resolve: {
root: path.join(__dirname, 'src'),
modulesDirectories: [ 'node_modules' ],
extensions: ['', '.js', '.jsx']
},
plugins,
output: {
path: `${__dirname}/public/`,
filename: jsName,
publicPath
},
module: {
loaders: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader')
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader!less-loader')
},
{ test: /\.gif$/, loader: 'url-loader?limit=10000&mimetype=image/gif' },
{ test: /\.jpg$/, loader: 'url-loader?limit=10000&mimetype=image/jpg' },
{ test: /\.png$/, loader: 'url-loader?limit=10000&mimetype=image/png' },
{ test: /\.svg/, loader: 'url-loader?limit=26000&mimetype=image/svg+xml' },
{ test: /\.(woff|woff2|ttf|eot)/, loader: 'url-loader?limit=1' },
{ test: /\.jsx?$/, loader: process.env.NODE_ENV !== 'production' ? 'react-hot!babel!eslint-loader' : 'babel', exclude: /node_modules/ },
{ test: /\.json$/, loader: 'json-loader' }
]
},
eslint: {
configFile: '.eslintrc'
},
devtool: process.env.NODE_ENV !== 'production' ? 'source-map' : null,
devServer: {
headers: { 'Access-Control-Allow-Origin': '*' }
}
};
src/server.js
...
const assetUrl = process.env.NODE_ENV !== 'production' ? 'http://localhost:8050' : '';
function renderHTML(componentHTML, initialState) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello React</title>
<link rel="stylesheet" href="${assetUrl}/public/styles.css">
<script type="application/javascript">
window.REDUX_INITIAL_STATE = ${JSON.stringify(initialState)};
</script>
</head>
<body>
<div id="react-view">${componentHTML}</div>
<script type="application/javascript" src="${assetUrl}/public/bundle.js"></script>
</body>
</html>
`;
}
...
any guesses?
In your file you have given the src of the css and bundle.js file to start with http://localhost:8050, but src is always given with relative to the file you are in
you should use
<link rel="stylesheet" href="../public/styles.css">
and
<script type="application/javascript" src="../public/bundle.js"></script>
The path comes from the assumption that public directory is directly outside the src directory.

how to add hooks after bundle update

EDIT:I need to directly generate <script type="text/javascript" src="..."></script> format to a text file which my web app(django or rails) template engine can directly include. So a json file seems not ok.
After webpack update the js file in webpack --progress --colors --watch mode, I want to :
Create a copy with hashed-filename to specified path.
execute some nodejs code that lists all filenames in specified directory and writes them to a text file.
Currently my config file is:
module.exports = {
entry: "./index.js",
output: {
path: __dirname,
filename: "bundle.[hash].js"
},
module: {
loaders: [
{ test: /\.css$/, loader: "style!css" },
{ test: /\.scss$/, loaders: ["style", "css", "sass"]},
],
}
};
For example, every time webpack generate a bundle.[hash].js file, it will first make its copy to /bar, then check all filenames in /bar and write these filenames to /foo/bar/js.txt in this format:
<script type="text/javascript" src="/bar/bundle.sdfklsld.js"></script>
<script type="text/javascript" src="/bar/bundle.jkljsdfd.js"></script>
I know this maybe done by bundle-update, but it's poorly documented.
assets-webpack-plugin will be solution for you.
Your webpack.config.js with this plugin should look something like this:
const path = require('path');
const AssetsPlugin = require('assets-webpack-plugin');
const assetsPluginInstance = new AssetsPlugin({
path: path.join(__dirname, 'foo', 'bar'),
filename: 'js.json'
});
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, 'bar'),
filename: "bundle.[hash].js"
},
module: {
loaders: [
{ test: /\.css$/, loader: "style!css" },
{ test: /\.scss$/, loaders: ["style", "css", "sass"]},
],
},
plugins: [
assetsPluginInstance
]
};
Now in path bar/ you should have file bundle.sdfklsld.js.
In file foo/bar/js.json you will have:
{
"main": {
"js": "/bar/bundle.sdfklsld.js"
}
}
From that point you are good to go and create your script tags with proper path to bundle files.
Edit - create file .txt with script tags
If you want to create your assets as a plain text file you can use processOutput method in assets-webpack-plugin.
const assetsPluginInstance = new AssetsPlugin({
path: path.join(__dirname, 'foo', 'bar'),
filename: 'js.txt', // change to .txt
// it can be little different in your scenario
// - currently I'm showing only one file,
// but you can tweak it to accept array of files
processOutput: function (assets) {
return `<script type="text/javascript" src="${assets.main.js}"></script>`;
}
});

Resources