Copy and modify html index file when building with webpack - javascript

My current structure of files goes like this
root/
prod/
index.html
prod.js
webpack.config.js
index.html
bundle.js
...
bundle.js and prod.js generated from different webpack builds (separate commands)
root/prod/index.html file
<script src="./prod.js">
rest of the html tags
root/index.html file
<script src="./bundle.js">
rest of the html tags
what i need to do is everytime i run the prod build (builds files under the prod folder) is to copy the root/index.html to root/prod/index.html but modify
this line <script src="./bundle.js">
to this <script src="./prod.js">
i currently using the copy-webpack-plug in but this does not have an option to modify a file when coping
There is any way to achieve this kind of behavior instead of changing root/prod/index.html manually every time (i mean replacing the script tag src attribute)?
my current webpack build looks like this
const path = require('path');
const fs = require('fs');
const gracefulFs = require('graceful-fs');
gracefulFs.gracefulify(fs);
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const entryPath = './entry-webpack.js';
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = (env) => {
console.log('NODE_ENV: ', env.NODE_ENV);
const ENV = env.NODE_ENV;
let plugins = [];
let enviroment;
let bundleName;
let exportPath;
if (ENV === 'prod') {
enviroment = 'production';
plugins = [
new UglifyJsPlugin({include: /\.min\.js$/}),
new CopyPlugin([
{ from: 'index.html', to: './prod/index.html' }
])
];
bundleName = 'prod';
exportPath = './prod/[name].js';
} else {
enviroment = 'development';
bundleName = 'bundle';
exportPath = './[name].js';
}
return {
context: path.join(__dirname, ''),
mode: enviroment,
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
},
{
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000',
}
]
},
plugins,
entry: {
[bundleName]: entryPath,
},
output: {
path: __dirname,
filename: exportPath,
}
};
};

Not a solution but a workaround using webpack-shell-plugin
adding this to webpack plugins (executes anything pre and after build)
new WebpackShellPlugin(
{
onBuildStart:['echo "---Webpack Start---"'],
onBuildEnd:['node replace-script']
})
plus replace-script.js script
const fs = require('fs')
fs.readFile('./docs/index.html', 'utf8', (err, data) =>
err ? console.log("ERROR" + err)
: fs.writeFile(
'./docs/index.html',
data.replace(`<script src="bundle.js">`, `<script src="prod.js"></script>`),
'utf8',
(err) =>
err ? console.log("ERROR" + err)
: console.log("SUCCESS")
)
);
a better solution without the need to add a new script would be much appreciated

Related

Getting this error: You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file

Hi i'm building an app using express and node. When I compile my project with webpack either in prod or dev mode I get the error : "Uncaught Error: Module parse failed: Unexpected token (8:20). You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file."
Now I don't know what type of file needs the loaders so I can't figure out what kind of loader I need to install in webpack. I've been trying to search here and done some googling but to no avail. If you can help me out with this I'd be really grateful!
My server.js:
const dotenv = require('dotenv');
dotenv.config();
function MeaningCloud(obj) {
return obj;
}
var textapi = new MeaningCloud({
application_key: process.env.API_KEY
});
projectData = {};
// Setup empty JS object to act as endpoint for all routes
projectData = {};
// Require Express to run server and routes
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
// Start up an instance of app
const app = express();
/* Middleware*/
//Here we are configuring express to use body-parser as middle-ware.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Cors for cross origin allowance
app.use(cors())
// Initialize the main project folder
app.use(express.static('client'));
app.get("/all", function sendData(req, res) {
res.send(projectData);
})
app.post("/add", (req, res) => {
projectData['lat'] = req.body.lat;
projectData['lng'] = req.body.lng;
projectData['countryCode'] = req.body.countryCode;
projectData['image'] = req.body.image;
res.send(projectData);
})
app.get("/", (req, res) => {
res.sendFile("index.html", { root: "src/client/views" })
})
// Setup Server
app.listen(3000, () => {
console.log("Listening on port 3000")
console.log("Go to http://localhost:3000")
})
My index.js:
const geoURL = "http://api.geonames.org/searchJSON?";
const geoUsername = `${process.env.GEO_USERNAME}`;
const weatherKey = `${process.env.WEATHER_KEY}`;
const weatherURL = "https://api.weatherbit.io/v2.0/current?";
const pixabayKey = `${process.env.PIXABAY_KEY}`;
const pixabayURL = "https://pixabay.com/api/?";
import getCity from getCity;
import getWeather from getWeather;
import getImage from getImage;
import postData from postData;
import receiveData from receiveData;
let d = new Date();
let newDate = d.getMonth() + 1 + "." + d.getDate() + "." + d.getFullYear();
const submitBtn = document.getElementById("submitBtn");
submitBtn.addEventListener("click", (e) => {
const city = document.getElementById("city");
if (city !== "") {
getCity(geoURL, city, geoUsername)
.then(city => getImage(pixabayURL, city, pixabayKey))
.then(cityData => getWeather(weatherKey, weatherURL, cityData))
.then(function (data) {
postData("/add", { lat: data.lat, lng: data.lng, countryCode: data.countryCode, image: data.image })
}).then(function () {
receiveData()
}).catch(function (error) {
console.log(error);
alert("Invalid city");
})
}
})
The error occurs at line 8 of the above file
so my getCity.js:
const getCity = async (geoURL, city, geoUsername) => {
const res = await fetch(`${geoURL}q=${city}&username=${geoUsername}`);
try {
const cityData = await res.json();
return cityData;
}
catch (error) {
console.log("error", error);
}
}
module.exports(getCity)
Also my webpack.config.js file! Sorry about that before!
const path = require('path')
const webpack = require('webpack')
const HtmlWebPackPlugin = require("html-webpack-plugin")
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
entry: './src/client/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name][contenthash].js',
clean: true,
},
mode: 'development',
devtool: 'source-map',
stats: 'verbose',
module: {
rules: [
{
test: '/\.js$/',
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./src/client/views/index.html",
filename: "./index.html",
}),
new CleanWebpackPlugin({
// Simulate the removal of files
dry: true,
// Write Logs to Console
verbose: true,
// Automatically remove all unused webpack assets on rebuild
cleanStaleWebpackAssets: true,
protectWebpackAssets: false
})
]
}
Update: So I thought it was something to do with images. So I updated the config.js to include that loader. But it still gives me the same error on the same line.
Change the module rule test for babel-loader to a Regular Expression instead of a String.
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
Also, include quotes around the module name after from and move the import statements to the top of the file:
// For example
import getCity from "getCity";

How to import a namespaced css from node_modules in the entries of a rewired create-react-app? Is react-app-rewire-multiple-entry the way to go?

I have a create-react-app not ejected and rewired with react-app-rewired and customized with customize-cra.
This is the scenario and I currently can't change it.
Here it is the configuration of config-overrides.js:
const path = require('path')
const fs = require('fs')
const {
override,
overrideDevServer,
watchAll,
removeModuleScopePlugin,
addWebpackModuleRule
} = require('customize-cra')
const theme = process.env.REACT_APP_THEME
const currentDirectory = fs.realpathSync(process.cwd())
const resolveApp = relativePath => path.resolve(currentDirectory, relativePath)
module.exports = {
webpack: override(
removeModuleScopePlugin(),
addWebpackModuleRule({
test: /\.svg$/,
loader: 'raw-loader',
include: [
resolveApp('../source/' + theme + '/icons/dist')
]
})
),
devServer: overrideDevServer(
watchAll()
),
paths: paths => {
paths.appBuild = path.join(paths.appBuild, theme)
return paths
}
}
A new need now is to import in the app some css from a local package, setup in package.json with a local namespace
"dependencies": {
"#namespace/helpers": "*",
I thought to use react-app-rewire-multiple-entry that seems the perfect lib to import multiple entries for a rewired create-react-app
Here is the new update:
const path = require('path')
const fs = require('fs')
const {
override,
overrideDevServer,
watchAll,
removeModuleScopePlugin,
addWebpackModuleRule
} = require('customize-cra')
const theme = process.env.REACT_APP_THEME
const currentDirectory = fs.realpathSync(process.cwd())
const resolveApp = relativePath => path.resolve(currentDirectory, relativePath)
// new css entries configuration
const cssEntries = require('react-app-rewire-multiple-entry')([
{
entry: './src/index.tsx',
helpers_1: '../node_modules/#namespace/helpers/dist/index.css',
helpers_2: '#namespace/helpers/dist/index.css'
}
])
module.exports = {
webpack: override(
cssEntries.addMultiEntry, // new css entries
removeModuleScopePlugin(),
addWebpackModuleRule({
test: /\.svg$/,
loader: 'raw-loader',
include: [
resolveApp('../source/' + theme + '/icons/dist')
]
})
),
devServer: overrideDevServer(
watchAll()
),
paths: paths => {
paths.appBuild = path.join(paths.appBuild, theme)
return paths
}
}
But both approaches currently implemented (first helpers_1: '../node_modules/#namespace/helpers/dist/index.css', and then helpers_2: '#namespace/helpers/dist/index.css') are not loading the css in the create-react-app.
Any ideas to fix it?
Or something wrong that you see?
Thanks in advance

How to call the public directory from functions directory in Google cloud functions

I'm using Loadable components in my react Server side rendering site. It works well in localhost, then I tried to run the site in google cloud function I am getting The "path" argument must be of type string. Received undefined error.
Here I have added the code I used to call loadable-stats.json file
const nodeStats = path.resolve(
__dirname,
'../../public/dist/async-node/loadable-stats.json',
)
const webStats = path.resolve(
__dirname,
'../../public/dist/web/loadable-stats.json',
)
Here is the screenshot of my code structure
My firebase.json file
{
"hosting": {
"public": "public",
"rewrites": [
{
"source": "**",
"function": "supercharged"
}
]
}
}
Here I have added server index.js file
import express from 'express';
import renderer from './server/renderer';
import createStore from './server/createStore';
import Routes from './public/Routes'
import {matchRoutes} from 'react-router-config';
const functions = require('firebase-functions');
import path from 'path'
const app = express();
app.use(express.static(path.join(__dirname, '../public')))
app.get('*.js', function (req, res, next) {
req.url = req.url + '.gz';
res.set('Content-Encoding', 'gzip');
next();
});
function handleRender(req, res) {
const store = createStore(req)
//incoming request path or page that user is trying to fetch
//and it's going to look at our router configuration object
//and deside what set of component need to render
const promises = matchRoutes(Routes, req.path).map(({route}) => {
return route.loadData? route.loadData(store, req.path) : null;
});
Promise.all(promises).then(() => {
res.set('Cache-Control', 'public, max-age=600, s-maxage=1200');
// Send the rendered page back to the client.
res.send(renderer(req, store));
});
}
// This is fired every time the server-side receives a request.
app.use(handleRender);
//const port = 3000;
//app.listen(port);
exports.supercharged = functions.https.onRequest(app);
My webpack config file
import path from 'path'
import nodeExternals from 'webpack-node-externals'
import LoadablePlugin from '#loadable/webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
const DIST_PATH = path.resolve(__dirname, 'public/dist')
const production = process.env.NODE_ENV === 'production'
const development =
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
const getConfig = target => ({
name: target,
mode: development ? 'development' : 'production',
target,
entry: `./src/public/index-${target}.js`,
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
caller: { target },
},
},
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
],
},
],
},
externals:
target === 'async-node'
? ['#loadable/component', nodeExternals()]
: undefined,
output: {
path: path.join(DIST_PATH, target),
filename: production ? '[name]-bundle-[chunkhash:8].js' : '[name].js',
publicPath: `/dist/${target}/`,
libraryTarget: target === 'async-node' ? 'commonjs2' : undefined,
},
plugins: [new LoadablePlugin(), new MiniCssExtractPlugin()],
})
export default [getConfig('web'), getConfig('async-node')]
This code works when I run my project using NODE_ENV=production node functions/main.js. This code is not working when I run in google cloud function firebase serve --only functions,hosting.
Suggession or advice will be more helpful for me.

webpack server serves different index.html files under different directories in multiple page application

I am developing a Web application of multiple pages with Webpack. In develop environment, I want Webpack server to open the index.html files under the different directories in url according to the file path, for example: http://localhost/index/file/to/the/directories/, then the index.html file serve automatically, without typing index.html in the url. The Webpack server using the plugins: webpack-dev-middleware, webpack-hot-middleware. Is there a way to achieve this mission?
The project directory is like below:
-build
-dev-server.js
-webpack.conf.js
-src
-directoryA
-mainA.js
-directoryB
-mainB.js
-template
-mainA.html
-mainB.html
Vue.js used in the project, and the code below is simplified.
The webpack.conf.js:
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
mainA: './src/directoryA/mainA.js',
mainB: './src/directoryB/mainB.js',
},
output: {
path: './src'
filename: '[name].js',
publicPath: '/'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'directoryA/index.html',
template: 'template/mainA.html',
inject: true,
chunks: ['mainA'],
}),
new HtmlWebpackPlugin({
filename: 'directoryB/index.html',
template: 'template/mainB.html',
inject: true,
chunks: ['mainB'],
}),
],
}
The dev-server.js is below:
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var webpackConfig = require('./webpack.conf')
var port = process.env.PORT || config.dev.port
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// serve webpack bundle output
app.use(devMiddleware)
app.use(hotMiddleware)
var uri = 'http://localhost:' + port
var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})
console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
_resolve()
})
var server = app.listen(port)
module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}
Now, I start the server, I open the url in the browser: http://localhost:3000/directoryA/. I hope it will open the index.html file under the directory, but it isn't. How can I let it work?

How to import javascript export file generated from the webpack.config?

Problem description
I need to set the DEV or Production environment so I can test API's either locally or get them ready for production.
ie: Either use http://localhost/api/app/login or /api/app/login
Now I was able to accomplish this by attaching NODE_ENV variables to my npm scripts in the package.json and a couple of lines of code in my webpack.config as so:
"scripts": {
"dev": "NODE_ENV=development webpack-dev-server --history-api-fallback",
"prod": "NODE_ENV=production webpack-dev-server -p",
webpack.config
const environment = process.env.NODE_ENV;
....
new CopyWebpackPlugin([{ from: "static" }])
^ that will create an env I can use in my services/api.js file. Now my Problem is that my Jest tests will fail every time because env is undefined.
Attempted solution - need help
So now instead what I'm trying to do is use node to actually generate a Javascript file that I can actually import directly into my services/api.js that way I avoid the undefined env error in testing.
I'm able to create a Javascript file with the following updates to my webpack.config
const fs = require('fs');
const webpack = require('webpack')
const environment = process.env.NODE_ENV;
// fs.writeFileSync('src/consts/env.txt', environment);
const stream = fs.createWriteStream("src/consts/endpoints.js");
stream.once('open', function(fd) {
stream.write('export const environment = () => "'+environment+'"');
stream.end();
});
The file it created (src/consts/endpoints.js):
export const environment = () => "development"
I've also tried this way:
export const environment = "development"
Now my updated services/api.js
import axios from 'axios'
import environment from '../consts/endpoints'
console.log('api.js environment:', environment);
However environment is undefined when I check out localhost.
How can I fix this problem? Is it a race condition? Is there another way to generate the file I need to import?
I tried to generate a .txt file however I can't import that file locally, can only do it in the cloud.
Full webpack.config file
const fs = require('fs');
const webpack = require('webpack')
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");
const dist = path.resolve(__dirname, "dist");
const src = path.resolve(__dirname, "src");
const environment = process.env.NODE_ENV;
// fs.writeFileSync('src/consts/env.txt', environment);
const stream = fs.createWriteStream("src/services/environment.js");
stream.once('open', function(fd) {
stream.write('export const environment = "'+environment+'"');
stream.end();
});
module.exports = {
context: src,
entry: [
"./index.js"
],
output: {
path: dist,
filename: "manage2.bundle.js",
publicPath: '/static/',
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: ["babel-loader"]
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallbackLoader: "style-loader",
loader: ["css-loader", "sass-loader"],
publicPath: dist
})
}
]
},
devServer: {
hot: false,
quiet: true,
publicPath: "",
contentBase: path.join(__dirname, "dist"),
compress: true,
stats: "errors-only",
open: true
},
plugins: [
new HtmlWebpackPlugin({
template: "index.html"
}),
new ExtractTextPlugin({
filename: "manage2.css",
disable: false,
allChunks: true
}),
new CopyWebpackPlugin([{ from: "static" }])
]
};
// new webpack.DefinePlugin({ env: JSON.stringify(environment) })
Figured it out!
I was importing the file incorrectly.
So from webpack.config
const environment = process.env.NODE_ENV;
const stream = fs.createWriteStream("src/services/environment.js");
stream.once('open', function(fd) {
stream.write('const env = "'+environment+'"\n');
stream.write('export default env');
stream.end();
});
^ That generates this file (src/services/environment.js) which contains:
const env = "development"
export default env
Finally in my services/api.js file I can use the import statement the way I wanted, I was missing the export default from above.
import axios from 'axios'
import endpoints from './endpoints'
import env from './environment'
const api = endpoints(env);
console.log('api', api);
const log = (method, err) => {
console.error(`%c${method}`, 'background: #393939; color: #F25A43', err);
return err;
};
export const userLogin = (username, password) => {
const post_data = { username, password };
return axios.post(api.login, post_data)
.then(res => res)
.catch((err) => log('api.userLogin', err));
};
And in my package.json I can change the ENV var to spit out "development" or "production".
"scripts": {
"dev": "NODE_ENV=development webpack-dev-server --history-api-fallback",
"prod": "NODE_ENV=production webpack-dev-server -p",

Categories