Webpack CopyPlugin modifying JS Files - javascript

I'm working on a React app being served by Flask.
I need the app to present some things that are filled by the Flask app using a Jinja template, and the simplest way I could find that should work is to use an external js file which I run through a render_template command and have the rest of the code reference that.
I used the WebpackCopyPlugin to make sure that file is available to import, and I explicitly exclude it from the babel-loader to make sure it doesn't get compiled.
However, when it is copied to the output dir by npm run build, it changes its contents!
This is the original file:
var is_admin = "{{is_admin}}" == "True";
var is_user = "{{is_user}}" == "True";
var is_debug = "{{is_debug}}" == "True";
var username = "{{request.remote_user}}";
(Yes, I know I shouldn't be keeping stuff like that in a javascript file, it doesn't actually give permissions to do anything - it's just for display purposes. The actual permission checking and access granting is all done in the backend).
But WebpackCopyPlugin copies it to look like this:
var is_admin=!1,is_user=!1,is_debug=!1,username="{{request.remote_user}}";
Why is it doing that?
Can I tell it to just copy the file as is without modifying it?
Thanks!

Modify the default minimizer options.
webpack.config.js
{
optimization: {
minimize: true,
minimizer: [new TerserPlugin({
exclude: /static/,
})]
}
}

Related

How to do cache busting in Rollup.js?

In my project, I need to do cache busting, since after a new deploy, the browser often only reloads the HTML but not the JS & CSS files.
Currently, I am not building the HTML in any way, it just already sits in the public directory.
The simplest approach seems to be to add a timestamp to the JS reference:
<script type="module" src="bundle/index.js?ts=20201026-102300"></script>
Now, what is the best way to achieve this in a project that already uses rollup.js?
I have seen #rollup/plugin-html, yet I'm puzzled by the example in its documentation, as it takes a JS file as input:
input: 'src/index.js',
What JS file should that be?
Instead I expect that need to define
an input HTML file
some space for code to set the timestamp variable
an output HTML file
So what's the best way to do this, be it with #rollup/plugin-html or with another approach?
Came here looking for an answer to this question myself and a few moments later and a bit of regex fiddling, I got it to work.
Note: this solution edits your HTML file each time you build it. There is no input (template) HTML and output HTML.
Install rollup-plugin-replace-html-vars
npm install rollup-plugin-replace-html-vars --save-dev
Add this piece of config to your rollup.config.js file
// rollup.config.js
// ...
plugins: [
replaceHtmlVars({
files: '**/index.html',
from: /\.\/\w+\/\w+\.\w+.\w+\?v=\d+/g,
to: './dist/app.min.js?v=' + Date.now(),
}),
]
In your index.html, add this reference to the app.js:
<script type="module" src="./dist/app.min.js?v=1630086943272"></script>
Run rollup and the reference to app.js in your index.html will have a timestamp of the build time each time you run it.
Bonus:
If you don't have a .min in your filename, use this regex instead:
/\.\/\w+\/\w+\.\w+\?v=\d+/g
Full disclosure; I'm no regex wizard, just managed to hack this one together. I bet someone here will have a better way of capturing ./dist/app.min.js?v=1630086943272 with a regex but this works for my solution.
I went with using file hashes, which means it's only reloaded when there is a new version for that file.
For that, I wrote my own utility:
function escapeStringRegexp(string) {
if (typeof string !== 'string') {
throw new TypeError('Expected a string');
}
return string
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
.replace(/-/g, '\\x2d');
}
function insertHashToFile(options) {
return {
writeBundle(outputOptions) {
const outputDir = outputOptions.dir ? outputOptions.dir : path.dirname(outputOptions.file);
let indexHtml = fs.readFileSync(options.htmlFile, 'utf8');
for (const sourceFile of options.sourceFiles) {
const fb = fs.readFileSync(path.join(outputDir, sourceFile));
const hash = crypto.createHash('sha1');
hash.update(fb)
const hexHash = hash.digest('hex');
const replacePattern = new RegExp(escapeStringRegexp(sourceFile) + '(:?\\?h=[^"]+)?', 'g');
indexHtml = indexHtml.replaceAll(replacePattern, `${sourceFile}?h=${hexHash.substring(0, 8)}`);
}
fs.writeFileSync(options.htmlFile, indexHtml);
},
};
}
and then
plugins: [
production && insertHashToFile({
sourceFiles: [
"bundle.js",
"bundle.css",
],
htmlFile: "public/index.html",
}),
]

Environment variable is undefined in electron even it has been set inside webpack.DefinePlugin

I have a requirement where we need to set dll path based upon whether it is executing in production or in development environment. So I decided to place that value in environment variable and tried to achieve that using webpack.DefinePlugin({}).
Method 1:
webpack.config.json
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV' : JSON.stringify('production')
})
And then I tried to get that value in electron's main process, In my case elec.js
elec.js
const Electron = require("electron");
const app = require("electron");
var dllPath = "";
function createWindow() {
let win = new BrowserWindow({
width: 800,
height: 600,
title: "Test",
icon: "Test.ico"
});
win.setMenu(null);
win.loadURL(
url.format({
pathname: path.join(__dirname, "../renderer/index.html"),
protocol: "file:",
slashes: true
})
);
if (process.env.NODE_ENV ==='production') {
dllPath = path.join(
__dirname,
"./../../dll/test.dll"
);
} else {
dllPath = path.join(
__dirname,
"./../../../dll/test.dll"
);
}
}
app.on("ready", createWindow);
But problem is that when I try to access that value in createWindow() function it is undefined so flow always goes to else block.
Is there anything I am missing?
Method 2:
I tried to achieve the same using cross-env node package, but no luck. Please find below code block which I tried using cross-env.
package.json
"scripts": {
"build": "cross-env process.env.NODE_ENV=production && rimraf ./dist/ && webpack --progress && node-sass
./src/renderer/scss/ -o ./dist/renderer/ && rimraf ./dist/renderer/includes/"
}
The problem is multi-faceted.
First, your elec.js is executed by Electron before the app is loaded. Electron runs elec.js, which creates the Browser window (let win = new BrowserWindow(...)) and loads HTML file (win.loadURL(...)) into it inside the browser process, the HTML then loads your webpack'ed js. So none of the webpacked js code is available in the elec.js. The webpack'ed code is also running in another process than the elec.js.
Another thing to note is that webpack plugin does not create any assignment to the variable it points too. It is done by simple text search and replace, in your example, all instances of process.env.NODE_ENV will be replaced with "production" string in the source code that is webpack'ed. That is not too obvious, but messes up the expected results.
One last thing - webpack plugin does not change any code in elec.js file, as that file is not webpack'ed.
So all that makes process.env.NODE_ENV from the build/webpack time not available in the elec.js code.
Once the mechanisms are clear, there are few ways to solve the problem, I will give general ideas, as there are plenty of discussions on each, and depending on circumstances and desired use case, some are better than others:
Generate a js file with necessary assignments based on environment variable during build (e.g. copy one of env-prod.js / env-dev.js -> env.js), copy it next to the elec.js, and reference it (require(env.js)) in elec.js.
Pass environment variable from command line (e.g. NODE_ENV=1 electron .) - it will get to elec.js.
Include a file into webpack based on environment variable (e.g. copy one of env-prod.js / env-dev.js -> env.js) and peek into webpacked' files from elec.js, e.g. using asar commands.
Use different version in package.json depending on build (e.g. version: "1.0.0-DEBUG" for debug), and read & parse it by calling app.getVersion() in elec.js. It is tricky as package.json should be a single file, but OS commands could be used (e.g. in "scripts") to copy one of prepared package.json files before invoking npm.
Here are some links that could help too:
Electron issue #7714 - discussion on relevant features in Electron
electron-is-dev - module checking if it is in dev
Electron boilerplate - example boilerplate that uses config/env-prod/dev files
The insight provided by iva2k is what allowed me to come to a solution for this same problem.
Using dotenv to create a .env file for my config got me halfway to where I wanted to be (setting up a few environment variables for use in a production setting). The problem then became that Electron wasn't passing those from the Main process down to the Renderer process by default.
The work-around is simple: use Electron's own ipcMain and ipcRenderer modules to pass the dotenv object between the two.
In your main file (e.g. your elec.js file), place an ipcMain event listener after requiring the module:
const config = require('dotenv').config();
const electron = require('electron');
const { app, BrowserWindow, ipcMain } = electron;
...
ipcMain.on('get-env', (event) => {
event.sender.send('get-env-reply', config);
});
Elsewhere, in your application's rendering-side, place this anywhere necessary:
async function getConfig()
{
const { ipcRenderer } = window.require('electron');
let config = null;
ipcRenderer.on('get-env-reply', (event, arg) => {
// The dotenv config object should return an object with
// another object inside caled "parsed". Change this if need be.
config = arg.parsed;
});
ipcRenderer.send('get-env');
return config;
}
This basically allowed me to declare one event in the Main process file, and then re-use it in any process-side file I wanted, thus allowing me to obfuscate config variables in a file that goes with the build, but isn't accessible to end-users without opening up the dev-tools.
Maybe late but can use simple hack in elec.js
const isProduction = process.env.NODE_ENV === 'production' || (!process || !process.env || !process.env.NODE_ENV);
In your console
For Windows
set MY_VARIABLE=true
For linux
$ export MY_VARIABLE=true
window.process.env.MY_VARIABLE

gulp/minify: index.html gets cryptic extension in file name, how to take advantage?

I am minifying an index.html file with gulp (note: took over this project, build system has been done by former dev).
It all works fine, but the gulp task generates a HTML file which has a cryptic extension to it, like:
index-bd2c7f58f0.html
I understand this must have it's advantage, but I can't grasp what...:) Because the disadvantage now is:
The node server needs the presence of an index.html file to allow the '/' route to work.
Thus so far, I either have to copy the file on every build or create a link which needs to be updated on every build
What am I missing? Should I just instruct gulp to create a plain index.html file, or what are best practices here?
Also, which of the various plugin calls is actually responsible for attaching that extension to the file name?
EDIT: Seems to be the gulp-rev and revReplace calls
Here is the gulp task I am using:
gulp.task('html', ['styles', 'scripts'], function () {
var client = buildHTML('./client/index.html', './dist/public');
return merge(client);
});
function buildHTML(index, distFolder) {
var lazypipe = require('lazypipe');
var saveHTML = lazypipe()
.pipe($.htmlmin, {
removeComments: true,
removeOptionalTags: true
})
.pipe(gulp.dest, distFolder);
return gulp.src(index)
.pipe($.useref())
.pipe($.rev())
.pipe($.revReplace({replaceInExtensions: ['.js', '.css', '.html', '.ejs']}))
.pipe($.if('*.html', saveHTML()));
}
One advantage that I'm familiar with is when it's used with assets, when you recompile the asset and create a new fingerprint for that file, the request won't return the cached response because it's a different file. As for your problem, you probably shouldn't be adding that has to your index, I think it's pretty unorthodox

grunt configure settings file dynamically

I have two files that get loaded, shown below. I want to dynamically load the settings file by name in properties.js. I want to replace <%= settings["build.target.environment"] %> with the value from the environment build option. Whenever i try to replace the value i get a compilation error in grunt. Basically, cant load tasks, X task isnt found because no tasks loaded properly. I have an alias called init that runs both of these items in order. How can i get the build option into the properties file so i can dynamically load the config i want to load. or is there another option? It seems like my build option wouldnt be picked up at the time these files load.
please bear with me and any ignorances with grunt, relatively new to grunt.
properties.js:
module.exports = {
settings: 'config/build.properties',
instance: 'config/environment.<%= settings["build.target.environment"] %>.properties'
}
buildoptions.js:
module.exports = function(grunt) {
grunt.registerTask('buildoptions', 'set build version and repo branch', function() {
var build = grunt.option("build");
var branch = grunt.option("branch") || '';
var environment = grunt.option("environment");
grunt.config('build', build);
grunt.config('branch', branch);
grunt.config('environment', environment)
});
};
aliases.json:
"init": [
"buildoptions",
"properties"
]
You don't need to set local variables to access grunt.option values within template strings, instead reference it directly:
module.exports = {
settings: 'config/build.properties',
instance: "config/environment.<%= grunt.option('environment') %>.properties"
}

Minify Scripts/CSS in production mode with node.js

I have a web app that runs in node. All the (client) Javascript/CSS files are not minified at the moment to make it easier to debug.
When I am going into production, I would like to minify these scripts. It would be nice to have something like:
node app.js -production
How do I serve the minified version of my scripts without changing the script tags in my html files? There should be something like: if I am in production, use these 2 minified(combined) scripts, else use all my unminified scripts..
Is this possible? Maybe I am thinking too complicated?
You might be interested in Piler. It's a Node.js module that delivers all the JavaScript (and CSS) files you specify as usual when in debug mode, but concatenated and minified when in production mode.
As a special feature, you can force CSS updates via Socket.io in real-time to appear in your browser (called "CSS Live Updated" in Piler), which is quite awesome :-).
The trick is that inside your template you only have placeholders for the script and link elements, and Piler renders these elements at runtime - as single elements in debug mode, and as a dynamically generated single element in production mode.
This way you can forget about creating concatenated and minified versions of your assets manually or using a build tool, it's just there at runtime, but you always have the separated, full versions when developing and debugging.
you could use 2 separate locations for your static files
Here's some express code:
if (process.env.MODE === "production") {
app.use(express['static'](__dirname + '/min'));
} else {
app.use(express['static'](__dirname + '/normal'));
}
and start node with
MODE=production node app.js
Furthermore, if you don't want to duplicate all your files, you could take advantage of the fact that express static router stops at the first file, and do something like this instead:
if (process.env.MODE === "production") {
app.use(express['static'](__dirname + '/min')); // if minized version exists, serves it
}
app.use(express['static'](__dirname + '/normal')); // fallback to regular files
Using the same name for minimized or not is going to cause problem with browser caching, though.
I want to share my final solution with you guys.
I use JSHTML for Express (enter link description here)
In my main node file I use a special route:
app.get('/**:type(html)', function (req, res, next) {
var renderingUrl = req.url.substring(1, req.url.lastIndexOf("."));
//TODO: Find a better solution
try{
var assetUrl = req.url.substring(req.url.lastIndexOf("/") + 1, req.url.lastIndexOf("."));
var assets = config.getResourceBundle(assetUrl);
assets.production = config.getEnviroment() === "production";
res.locals(assets);
res.render(renderingUrl);
}catch(e){
res.redirect("/");
}
});
As you can see, I get my assets from config.getResourceBundle. This is a simply function:
exports.getResourceBundle = function(identifier){
switch(enviroment){
case "development":
return devConfig.getResourceBundle(identifier);
case "production":
return prodConfig.getResourceBundle(identifier);
default:
return devConfig.getResourceBundle(identifier);
}
}
And finally an example for an asset file collection is here:
exports.getResourceBundle = function (identifier) {
return resourceBundle[identifier];
};
resourceBundle = {
index:{
cssFiles:[
"resources/dev/css/login.css",
"resources/dev/css/logonDlg.css",
"resources/dev/css/footer.css"
],
jsFiles:[
"resources/dev/js/lib/jquery/jquery.183.js",
"resources/dev/js/utilities.js",
"resources/dev/js/lib/crypto.3.1.2.js"
]
},
register:{
cssFiles:[
"resources/dev/css/login.css",
"resources/dev/css/modalDialog.css",
"resources/dev/css/footer.css"
],
jsFiles:[
"resources/dev/js/lib/jquery/jquery.183.js",
"resources/dev/js/utilities.js",
"resources/dev/js/lib/crypto.3.1.2.js",
"resources/dev/js/lib/jquery.simplemodal.js",
"resources/dev/js/xfiles.register.js"
]
}
(...)
I have 2 folders. dev / prod. grunt will copy the minified files into prod/.. and deletes the files from dev/...
And if the NODE_ENV variable is set to production, I will ship the minified versions of my scripts/css.
I think this is the most elegant solution at the moment.
There are build tool plugins for you, may help you gracefully solve this problem:
For Gulp:
https://www.npmjs.org/package/gulp-useref/
For Grunt:
https://github.com/pajtai/grunt-useref
Another Node.js module which could be relevant is connect-cachify.
It doesn't seem to do the actual minification for you, but it does let you serve the minified version in production, or all the original scripts in development, without changing the templates (thanks to cachify_js and cachify_css).
Seems it's not as feature-rich as Piler, but probably a bit simpler, and should meet all the requirements mentioned in the question.

Categories

Resources