Variable masking - javascript

I have a class defined in an index.js file like this
const BLOG = BLOG || {};
BLOG.ComponentFactory = class {
}
window.BLOG = BLOG;
Then, in a file init.js in a bundle, I try to access that var, the file is like this
const BLOG = BLOG || {};
BLOG.init = function() {
var c = new BLOG.ComponentFactory()
}
I get BLOG.ComponentFactory is not a constructor and I cannot understand why. Is the BLOG definition inside the file init.js masking the global var?
There is something strange: using Chrome debugger in the init function, I see Blog.ComponentFactory defined inside "Global", but if I add to the properties to watch, I see Blog.ComponentFactory = undefined
I'd like to understand what's happening.
I need to defin BLOG as a global var as I'm using ES6 together with old javascritpt code.
EDIT: if I use the following code all works (init.js)
const BLOG = BLOG || {};
BLOG.init = function() {
var c = new window.BLOG.ComponentFactory()
}
So, it seems the local const BLOG is masking the global BLOG var, but I need to define BLOG because otherwise I get BLOG is undefined. So, how do I solve the problem?
EDIT2: my webpack config (the problem is in the bundling, the vars are defined inside functions which are generated while bundling)
const webpack = require('webpack');
const path = require('path');
var config = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
]
},
// workaround for
// ERROR in ./node_modules/wpcom/build/lib/site.media.js
// Module not found: Error: Can't resolve 'fs' in '/node_modules/wpcom/build/lib'
node: {
fs: 'empty'
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
var aConfig = Object.assign({}, config, {
name: "a",
entry: "./WebContent/js/blog/index.js",
output: {
path: __dirname + '/WebContent/js/blogW',
filename: "bundle.js",
publicPath: 'http://localhost:8080/js/blogW'
},
devServer: {
historyApiFallback: true,
contentBase: './WebContent',
publicPath: "http://localhost:8080/js/blogW",
hot: true
}
});
module.exports = [
aConfig
];

Related

Stop webpack from instantiating module multiple times across different entry points

I'm generating a static HTML page with Webpack. I have a custom logging module, and then two other modules which import it. Those imports are in different entry points. The problem is, the logging module is actually being instantiated twice.
sendtolog.js:
'use strict';
import { v4 } from "uuid";
console.log('logging...');
const ssid = v4();
export default function sendToLog(metric) {
console.log(`Sending message with ${ssid}`);
}
webvitals.js:
import { getTTFB } from 'web-vitals/base';
import sendToLog from './sendtolog';
getTTFB(sendToLog);
pageactions.js:
'use strict';
import sendToLog from './sendtolog';
sendToLog({name: 'foo', value: 'bar'});
and then in the browser console:
[Log] logging...
[Log] Sending message with 53f50779-d430-49e1-a1be-5b1bb33db10b
[Log] logging...
[Log] Sending message with 415dd4b9-e089-4feb-a4cf-d29c12a26149
How do I get it to not do that?
The WebPack docs for optimization.runtimeChunk have a giant warning:
Imported modules are initialized for each runtime chunk separately, so
if you include multiple entry points on a page, beware of this
behavior. You will probably want to set it to single or use another
configuration that allows you to only have one runtime instance.
but I'm not using runtimeChunk, I'm just using splitChunks, which I assumed would be "another configuration that allows you to only have one runtime instance."
My WebPack config:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, argv) => {
console.log('Webpack mode: ', argv.mode);
const config = {
entry: {
'main': [
path.resolve(__dirname, './src/pageactions.js'),
],
'inline': [ path.resolve(__dirname,
'./node_modules/web-vitals/dist/polyfill.js'),
path.resolve(__dirname, './src/webvitals.js')
]
},
module: {
rules: [
// JavaScript
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './views/index.ejs'),
filename: path.resolve(__dirname, 'index.html'),
output: {
path: path.resolve(__dirname, './public'),
filename: '[name].bundle.js'
},
optimization: {
splitChunks: {
chunks: 'all'
},
},
devtool: ('production' === argv.mode) ? false : 'eval',
mode: argv.mode
}
return config;
};

Webpack file-loader, how to use outputPath function

I have been working in a project where I need to use the outputPath function from the file-loader, to emit files to different folders, but I had difficulties to understand and making it work.
Part of my code is as follows:
const path = require('path');
const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const fs = require('fs'); // fs stands for file system, it helps accessing phyisical file systems
const BRANDS = {
br1: 'br1.local',
b22: 'br2.local'
};
module.exports = {
mode: 'production',
entry: {
main: './src/js/main.js'
},
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]',
outputPath: (url, resourcePath, context) => {
if (/my-custom-image\.png/.test(resourcePath)) {
return `other_output_path/${url}`;
}
if (/images/.test(context)) {
return `image_output_path/${url}`;
}
return `output_path/${url}`;
}
}
},
]
},
documentation says that resourcePath is the absolute path to assets, I am not sure about this, as my assets folder has another name not assets, does it matter? it looks like: /src/images.
What is context not sure what is my context. When I do a console.log of these arguments it shows undefined, and it doesn't emit the images to the right folders.
You just need to add your folder name inside if statement.
If your folder tree looks like this:
/src/images/subfolder/
Change your code to this:
outputPath: (url, resourcePath) => {
if (/subfolder/.test(resourcePath)) {
return `images/subfolder/${url}`;
}
return `images/${url}`;
}

webpack watch mode with CopyWebpackPlugin causes infinite loop

In webpack, CopyWebpackPlugin causes infinite loop when webpack is in watch mode. I tried to add watchOptions.ignored option but it doesn't seem to work.
My webpack config is following:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const config = {
entry: {
'res': './src/index.js'
},
output: {
filename: '[name].min.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'production',
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new CopyWebpackPlugin([
{ from: 'dist', to: path.resolve(__dirname, 'docs/js') }
], {})
],
watchOptions: {
ignored: path.resolve(__dirname, 'docs/js')
}
};
module.exports = config;
Any help would be appreciated.
With CopyWebpackPlugin, I've experienced the infinite loop too. I tried all kinds of CopyWebpackPlugin configurations with no luck yet. After hours of wasted time I found I could hook into the compiler and fire off my own copy method.
Running Watch
I'm using webpack watch to watch for changes. In the package.json, I use this config so I can run npm run webpackdev, and it will watch for file changes.
"webpackdev": "cross-env webpack --env.environment=development --env.basehref=/ --watch"
My Workaround
I've added an inline plugin with a compiler hook, which taps into AfterEmitPlugin. This allows me to copy after my sources have been generated after the compile. This method works great to copy my npm build output to my maven target webapp folder.
// Inline custom plugin - will copy to the target web app folder
// 1. Run npm install fs-extra
// 2. Add fix the path, so that it copies to the server's build webapp folder
{
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
// Debugging
console.log("########-------------->>>>> Finished Ext JS Compile <<<<<------------#######");
let source = __dirname + '/build/';
// TODO Set the path to your webapp build
let destination = __dirname + '/../dash-metrics-server/target/metrics-dash';
let options = {
overwrite: true
};
fs.copy(source, destination, options, err => {
if (err) return console.error(err) {
console.log('Copy build success!');
}
})
});
}
}
The Workaround Source
Here's my webpack.config.js in total for more context. (In this webpack configuration, I'm using Sencha's Ext JS ExtWebComponents as the basis.)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { BaseHrefWebpackPlugin } = require('base-href-webpack-plugin');
const ExtWebpackPlugin = require('#sencha/ext-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const fs = require('fs-extra');
module.exports = function(env) {
function get(it, val) {if(env == undefined) {return val;} else if(env[it] == undefined) {return val;} else {return env[it];}}
var profile = get('profile', '');
var emit = get('emit', 'yes');
var environment = get('environment', 'development');
var treeshake = get('treeshake', 'no');
var browser = 'no'; // get('browser', 'no');
var watch = get('watch', 'yes');
var verbose = get('verbose', 'no');
var basehref = get('basehref', '/');
var build_v = get('build_v', '7.0.0.0');
const isProd = environment === 'production';
const outputFolder = 'build';
const plugins = [
new HtmlWebpackPlugin({template: 'index.html', hash: false, inject: 'body'}),
new BaseHrefWebpackPlugin({ baseHref: basehref }),
new ExtWebpackPlugin({
framework: 'web-components',
toolkit: 'modern',
theme: 'theme-material',
emit: emit,
script: './extract-code.js',
port: 8080,
packages: [
'renderercell',
'font-ext',
'ux',
'd3',
'pivot-d3',
'font-awesome',
'exporter',
'pivot',
'calendar',
'charts',
'treegrid',
'froala-editor'
],
profile: profile,
environment: environment,
treeshake: treeshake,
browser: browser,
watch: watch,
verbose: verbose,
inject: 'yes',
intellishake: 'no'
}),
new CopyWebpackPlugin([{
from: '../node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js',
to: './webcomponents-bundle.js'
}]),
new CopyWebpackPlugin([{
from: '../node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js.map',
to: './webcomponents-bundle.js.map'
}]),
// Debug purposes only, injected via script: npm run-script buildexample -- --env.build_v=<full version here in format maj.min.patch.build>
new webpack.DefinePlugin({
BUILD_VERSION: JSON.stringify(build_v)
}),
// This causes infinite loop, so I can't use this plugin.
// new CopyWebpackPlugin([{
// from: __dirname + '/build/',
// to: __dirname + '/../dash-metrics-server/target/test1'
// }]),
// Inline custom plugin - will copy to the target web app folder
// 1. Run npm install fs-extra
// 2. Add fix the path, so that it copies to the server's build webapp folder
{
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
// Debugging
console.log("########-------------->>>>> Finished Ext JS Compile <<<<<------------#######");
let source = __dirname + '/build/';
// TODO Set the path to your webapp build
let destination = __dirname + '/../dash-metrics-server/target/metrics-dash';
let options = {
overwrite: true
};
fs.copy(source, destination, options, err => {
if (err) return console.error(err)
console.log('Copy build success!');
})
});
}
}
];
return {
mode: environment,
devtool: (environment === 'development') ? 'inline-source-map' : false,
context: path.join(__dirname, './src'),
//entry: './index.js',
entry: {
// ewc: './ewc.js',
app: './index.js'
},
output: {
path: path.join(__dirname, outputFolder),
filename: '[name].js'
},
plugins: plugins,
module: {
rules: [
{ test: /\.(js)$/, exclude: /node_modules/,
use: [
'babel-loader',
// 'eslint-loader'
]
},
{ test: /\.(html)$/, use: { loader: 'html-loader' } },
{
test: /\.(css|scss)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader' }
]
}
]
},
performance: { hints: false },
stats: 'none',
optimization: { noEmitOnErrors: true },
node: false
};
};
So I know this question is very old at this point, but I was running into this endless loop issue again recently and found a couple of solutions.
Not the cleanest method, but if you add an "assets" folder, which can be completely empty, to the root of your project, it seems to only compile after your sources folder changes.
The better method I have found is within the webpack config. The original poster mentioned about using ignored which does seem to fix the issue if you instruct webpack to watch file changes after the initial build and to ignore your dist/output folder...
module.exports = {
//...
watch: true,
watchOptions: {
ignored: ['**/dist/**', '**/node_modules'],
},
};

How to make a javascript webpack module made for the browser safe to load in node environment?

I am trying to upgrade an old framework I built in javascript to es6/module standards and I have a lot of troubles.
One of my current problems is that due to server side rendering my modules are sometime loaded in the node environment and are trying to access the window, causing errors.
Is there a principled way to manage this ?
The main jQuery file has a nice failback if window is undefined and can load in node without a fuss. I am trying to implement this in web-pack by I am stumbling.
This is my current webpack config
// #flow
// import path from 'path'
import webpack from 'webpack'
const WDS_PORT = 7000
const PROD = JSON.parse(process.env.PROD_ENV || '0')
const libraryName = 'experiment'
const outputFile = `${libraryName}${PROD ? '.min' : '.max'}.js`
const plugins = [
new webpack.optimize.OccurrenceOrderPlugin(),
]
const prodPlugins = plugins.concat(new webpack.optimize.UglifyJsPlugin())
// not really working
export default {
entry: './builder.js',
target: 'web',
output: {
path: `${__dirname}/lib`,
filename: outputFile,
library: libraryName,
libraryTarget: 'umd',
umdNamedDefine: true,
},
module: {
loaders: [
{
test: /(\.jsx|\.js)$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/,
},
],
},
devtool: PROD ? false : 'source-map',
resolve: {
extensions: ['.js', '.jsx'],
},
externals: {
chartjs: {
commonjs: 'chartjs',
amd: 'chartjs',
root: 'Chart', // indicates global variable
},
lodash: {
commonjs: 'lodash',
amd: 'lodash',
root: '_', // indicates global variable
},
jquery: 'jQuery',
mathjs: {
commonjs: 'mathjs',
amd: 'mathjs',
root: 'math', // indicates global variable
},
'experiment-boxes': {
commonjs: 'experiment-boxes',
amd: 'experiment-boxes',
root: 'experimentBoxes', // indicates global variable
},
'experiment-babylon-js': {
commonjs: 'experiment-babylon-js',
amd: 'experiment-babylon-js',
root: 'EBJS', // indicates global variable
},
},
devServer: {
port: WDS_PORT,
hot: true,
},
plugins: PROD ? prodPlugins : plugins,
}
And this is my main entry point builder.js
/* --- Import the framwork --- */
import TaskObject from './src/framework/TaskObject'
import StateManager from './src/framework/StateManager'
import State from './src/framework/State'
import EventData from './src/framework/EventData'
import DataManager from './src/framework/DataManager'
import RessourceManager from './src/framework/RessourceManager'
import {
Array,
String,
diag,
rowSum,
getRow,
matrix,
samplePermutation,
rep,
Deferred,
recurse,
jitter,
delay,
looksLikeAPromise,
mustHaveConstructor,
mustBeDefined,
mandatory,
debuglog,
debugWarn,
debugError,
noop,
} from './src/framework/utilities'
/* add it to the global space in case user want to import in a script tag */
if (typeof window !== 'undefined') {
window.TaskObject = TaskObject
window.StateManager = StateManager
window.State = State
window.EventData = EventData
window.DataManager = DataManager
window.RessourceManager = RessourceManager
window.jitter = jitter
window.delay = delay
window.Deferred = Deferred
}
export {
TaskObject,
StateManager,
State,
EventData,
DataManager,
RessourceManager,
Array,
String,
diag,
rowSum,
getRow,
matrix,
samplePermutation,
rep,
Deferred,
recurse,
jitter,
delay,
looksLikeAPromise,
mustHaveConstructor,
mustBeDefined,
mandatory,
debuglog,
debugWarn,
debugError,
noop,
}
Am I on the right track?
Ok my solution so far, although feels like a hack, protects against require() in node environment.
In the ENTRY FILE of your webpack config check for window being defined.
Here is an example when trying to re-bundle babylonjs which relies heavily on window and would generate an error when required by node:
builder.js
let BABYLON = {}
let OIMO = {}
if (typeof window !== 'undefined') {
BABYLON = require('./src/babylon.2.5.full.max')
OIMO = require('./src/Oimo').OIMO
window.BABYLON = BABYLON
window.OIMO = OIMO
}
module.exports = { BABYLON, OIMO }
webpack.config.babel.js
import path from 'path'
import webpack from 'webpack'
const WDS_PORT = 7000
const PROD = JSON.parse(process.env.PROD_ENV || '0')
const plugins = [
new webpack.optimize.OccurrenceOrderPlugin(),
]
const prodPlugins = plugins.concat(new webpack.optimize.UglifyJsPlugin())
export default {
entry: [
'./builder.js',
],
output: {
filename: PROD ? 'babylon.min.js' : 'babylon.max.js',
path: path.resolve(__dirname, 'lib/'),
publicPath: `http://localhost:${WDS_PORT}/lib/`,
library: 'EBJS',
libraryTarget: 'umd',
umdNamedDefine: true,
},
module: {
rules: [
{ test: /\.(js|jsx)$/, use: 'babel-loader', exclude: /node_modules/ },
],
},
devtool: PROD ? false : 'source-map',
resolve: {
extensions: ['.js', '.jsx'],
},
devServer: {
port: WDS_PORT,
hot: true,
},
plugins: PROD ? prodPlugins : plugins,
}
Testing the bundle in node with a simple file like so:
bundle.test.js
const test = require('./lib/babylon.min.js')
console.log(test)
Will produce in the terminal:
$ node bundle.test.js
{ BABYLON: {}, OIMO: {} }

Best way to have all files in a directory be entry points in webpack?

I want to create multiple entry points for a website, which is pretty easily done in Webpack using an object for the entry property, like here.
But as the site grows (and it inevitably will) having to add each entry point seems cumbersome and prone to error. So I'd like to simply point at a directory and say "here are all the entry points."
So I've cooked this up:
var path = require('path');
var fs = require('fs');
var entryDir = path.resolve(__dirname, '../source/js');
var entries = fs.readdirSync(entryDir);
var entryMap = {};
entries.forEach(function(entry){
var stat = fs.statSync(entryDir + '/' + entry);
if (stat && !stat.isDirectory()) {
var name = entry.substr(0, entry.length -1);
entryMap[name] = entryDir + '/' + entry;
}
});
module.exports = {
entry: entryMap,
output: {
path: path.resolve(__dirname, '../static/js'),
filename: "[name]"
},
...
This works fine, but is there a feature or configuration option in Webpack that would handle this for me?
I think glob is the right way to go here (AFAIK webpack wont do this for you). This is what I ended up with, it will find all files in a directory and create an entry with a name matching the file:
var glob = require('glob');
var path = require('path');
module.exports = {
entry: glob.sync('../source/js/**.js').reduce(function(obj, el){
obj[path.parse(el).name] = el;
return obj
},{}),
output: {
path: path.resolve(__dirname, '../static/js'),
filename: "[name]"
},
...
adapt the search path to meet your specific needs. It might also be useful to pass in {cwd: someRoot} as the second argument to sync if you have a special scripts directory which will make this the new root of relative path searches.
In my opinion, only a little Node skill is needed, and it doesn't have to be that complicated.
const webpack = require('webpack');
const path = require('path');
const fs = require('fs');
const fileNames = fs.readdirSync('./src').reduce((acc, v) => ({ ...acc, [v]: `./src/${v}` }), {});
const config = {
entry: fileNames,
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]',
},
};
module.exports = config;
I have used Glob for this.
var path = require('path');
var glob = require('glob');
module.exports = {
entry: { 'app' : glob.sync('./scripts/**/*.ts*') },
output: {
path: path.join(__dirname, '/wwwroot/dist'),
filename: '[name].bundle.js',
sourceMapFilename: '[name].map'
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
}
]
},
resolve: {
extensions: [".ts", ".js"]
}
};

Categories