Webpage does not refresh in production until hard refresh - javascript

For my website, I have a flask server serving a file generated by webpack. Unfortunately, when I update the file, the webpage often does not update until a hard refresh (Ctrl-F5), due to browser caching. I want the webpage to update after a regular refresh, as most users do not know about hard refresh. In development, there are ways of getting around hard refresh, such as webpack-dev-server. What is the easiest way of doing this in production?
I have the following webpack.config.js file:
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: ['react-hot-loader/patch', './js/main.js'],
output: {
filename: "./static/bundle.js",
},
resolveLoader: {
moduleExtensions: ['-loader']
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loaders: 'babel',
query: {
presets: ['react', 'es2015', 'stage-0']
}
},
{
test: /\.css$/,
loader: 'style-loader',
},
{
test: /\.css$/,
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
]
}
};
The Flask server is serving an index.html file that looks like this:
<html>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>

You need to bust the cache yourself. Webpack has the provision for it.
output: {
filename: "./static/bundle-[hash:6].js",
},
The bundled file generated would look like: bundle-1e3dab.js
Now, in HTML:
<html>
<body>
<div id="app"></div>
<script src="bundle-1e3dab.js"></script>
</body>
</html>
Now every time you build, if something gets changed, the hash will be updated.
NOTE: Don't forget to update the file path in HTML each time you build or customize your build to have a replace task which automatically updates the HTML file. You can use Webpack Manifest Plugin for this.
UPDATE
Change entry in your webpack file:
// Entry, files to be bundled separately
entry: {
'main': [
'react-hot-loader/patch',
'./js/main.js'
]
},
And for updating the hash
var fs = require('fs');
var path = require('path');
var basePath = path.join(__dirname, '/');
function replace (statsData, fileName, readFilePath, regex, assetChunkName, writeFilePath, replaceWith) {
// Read the data so that hash can be read
let stats = statsData.toJson();
if (!stats.errors.length) {
// read the file i.e. index.html and store the contents
let contents = fs.readFileSync(path.join(readFilePath, fileName), 'utf8'),
// Replace the pattern with the user-defined replacedWith variable or the chunkHash webpack provides
htmlOutput = contents.replace(
regex,
replaceWith || stats.assetsByChunkName[assetChunkName][0]
);
// Write back the modified contents into the file
fs.writeFileSync(path.join(writeFilePath, fileName), htmlOutput);
}
}
inside the configuration, after module key, add the following code:
plugins: [
function() {
// To be executed when build is done
this.plugin('done', (statsData) => {
// `statsData` has the info regarding the file bundling(hash)
// Replace the filename with the update chunkHash for build/prod only
replace(
statsData,
'index.html', // filename which needs to be modified
path.join(basePath, '/dist/'), // path from where to read index.html
/bundle\.js/i, // regex i.e. which needs to be replaced
'bundle',
path.join(basePath, '/dist/')) // path from where to write index.html, can be same if needs ot override
})
}
]
Replace the pathname and you're done :)
let me know if you face any errors.

This is not a question about webpack, a server has no way to force browser don't use cache. What you can do is adding some postfix to your url like replacing bundle.js with bundle.js<it's md5>. In this case, as the url is different, browser will treat it as a new resource.

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

React import images from public folder

I'm trying to get static images fro my public directory but is not being found. I'm not using CRA, so maybe is some configuration with Webpack that I'm missing. Using file-loader module and importing the image works on Dev Mode, but doesn't work in for my production server specification
My Project structure:
\public
\static
\images
image.png
\src
\component
component.js
...
package.json
webpack.common.js
webpack.dev.js
webpack.prod.js
On component.js, I want to get image.jpg on static/images folder like this:
<img src='/static/images/image.png'></img>
But I'm getting a 404 not found.
My webpack.commom.js:
const CleanWebPackPlugin = require('clean-webpack-plugin')
const HtmlWebPackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
entry: {
main: './src/index.js'
},
output: {
filename: '[name].[hash].js',
path: path.resolve('./dist'),
publicPath: "/"
},
module:{
rules:[
{
test: /\.js$/,
exclude: ['/node_modules'],
use: [{ loader: 'babel-loader'}],
},
{
test: /\.s?(a|c)ss$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
},{
loader: 'sass-loader'
}]
},
{
test: /\.(png|jpe?g|gif)$/,
use: [
{
loader: 'file-loader',
options: {},
},
],
},
]
},
plugins: [
new HtmlWebPackPlugin({
template: 'index.html'
}),
new CleanWebPackPlugin(),
],
}
And the Dev version:
module.exports = merge(common, {
mode: 'development',
devServer: {
host: 'localhost',
port: 3000,
open: true,
historyApiFallback: true,
publicPath: "/",
}
})
Thank you in advance.
I assume you want to display such image more than once. In that case, is annoying to keep writing something like '%PUBLIC_URL%/img/static/images/image.png' or {process.env.PUBLIC_URL + '/img/static/images/image.png'} two, three or more times within your jsx code. Basically, without a plugin is imposible to import images from the public folder if your app is rooted in the src folder. However I did find a solution for me and was quite simple in fact. It was something like this:
import React from 'react'
var path = process.env.PUBLIC_URL;
var image = "/img/static/images/image.png";
and then, within jsx code:
<img src={path + image}/>
it worked for me, hope this is helpful for anyone! :D
You are using file-loader as webpack plugin, which does not work in the way of "just mirroring the directory structure of public to dist". You can find the documentation for that plugin here: https://webpack.js.org/loaders/file-loader/
Basically, what the plugin does is, if you import an image file (actually programatically importing it, not just using a string reference to its path), the file is copied to your dist directory, potentially renamed and than in your compiled source code the proper file name is inserted.
So in your case, if you want to solve your problem using file-loader, you would reference the image file like
// Relative path to image file from js file
import imageFile from './assets/image.png';
// ...
const component = props => <img src={imageFile}></img>;
If you want to use the approach of actually just mirroring a public-directory to the dist directory, you need to use an additional webpack-plugin for that (e.g. https://stackoverflow.com/a/33374807/2692307)
By the way, I assume it works in dev-mode because you are setting '/' as public path, so the development server just serves everything in the root directory as well. That is something different than copying the files to dist however as you are trying to achieve.
Not sure if this help but I'm will give it a try
Try to add public path to your dist folder also something like this
output: {
path: path.resolve(__dirname, "/dist"),
filename: "[name].js",
publicPath: "/dist/"
},

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.

Webpack, new chunk is loading in with wrong path

I am trying to chunk my app - attempting to follow webpacks guide on how-to (https://webpack.github.io/docs/code-splitting.html). So I have a seperate chunk set up for my app, I can see that webpack is generating 1.bundle.js in my build folder, however it is pasting it onto my index.html with an incorrect path, and in my console I see the fetch error for the 1.bundle.js file.
So my webpack config looks like so (im just using the webpack:dev for now):
return {
dev: {
entry: {
index: './client/app.jsx'
},
output: {
path: path.join(__dirname, '..', '..', 'dist', 'client'),
publicPath: "/dist/client/",
filename: 'bundle.js'
},
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
}, {
test: /\.json$/,
loader: 'json-loader'
}]
},
resolve: {
extensions: ['', '.js', '.jsx']
},
resolveLoader: {
fallback: [path.join(__dirname, 'node_modules')]
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
"NODE_ENV": JSON.stringify("dev")
}
})
]
},
and in my index.html I manually add <script src="bundle.js"></script> and that has been working great. It looks like when this builds now, webpack is applying its own script tag on my index like so
<script type="text/javascript" charset="utf-8" async="" src="/dist/client/1.bundle.js"></script>
However this path is incorrect, it should just be src="1.bundle.js". I have tried tweaking the path and publicPath but nothing seems to work. Is there a way to have webpack add the correct path? Thanks!
You should change publicPath for this snippet:
publicPath: "/"
It will always serve your chunks from root path.
Even though it is answered and accepted, I am providing additional helpful info for others with similar problems.
There are two different purposes for which the 2 parameters are used.
Output:path : The directory the bundle files mentioned in entry section are saved into. For example, the bundle.js for the 'entry' entry you had mentioned. In this case, it will be saved in webconfigfolder+"../../dist/client" folder.
Output: publicPath: The directory prefix that is added to refer to a module when accessed from browser. 0.bundle.js is an unnamed chunk created by code splitting. It will be placed in the output:path mentioned above but will be referred in your html using the public path.
So,if your files as in this case is stored in /dist/client folder, but the index.htm is served in /dist/client, you should give the public path as ./. If htm is served from /dist, the public path should be given as ./client/.
The public path is useful for chunks created for async loading which are called from browser dynamically.
This is because you have given reference to publicPath. So it will try to load the script from this publicPath though the file is not present there.
Removing publicPath can resolve the error

Categories

Resources