Named functions in a javascript file using Webpack 4 - javascript

I just started using Webpack 4 in a project and am new to Webpack. Once Webpack 4 was implemented, I noticed that named functions kept erroring saying [functionName] is not defined.
I have looked quite extensively over the last few days and have tried multiple options with no success. I am hoping for someone to help me work through this in a more direct fashion way.
function openNav(obj) {
...do something
}
#foreach (object in list)
{
...create some HTML
}
const bundleFileName = 'bundle';
const dirName = 'wwwroot/dist';
module.exports = (env, argv) => {
return {
mode: argv.mode === "production" ? "production" : "development",
entry: [
'./src/Index.js',
'./src/css/site.css',
'./src/js/app.js'
],
output: {
filename: bundleFileName + '.js',
path: path.resolve(__dirname, dirName)
},
module: {
rules: [
{
test: /\.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
}
]
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: bundleFileName + '.css'
})
]
};
};
I expected the button in the razor file to send the object over to the named function so something can happen like it did previously before webpack

You've said you're trying to use openNav from an onclick attribute. The problem there is that openNav has to be a global to be used that way, but Webpack is a module bundler. Functions at the top level of modules aren't globals. (Which is a good thing.)
The solution is to not use onxyz-attribute-style event handlers. Instead, use modern event handling (addEventListener, probably with at least some event delegation).
To ease your transition from the old way to the modern way, you can expose a function globally from within a module like this (on browsers):
window.openNav = openNav;
I strongly recommend only doing that temporarily to make it easier to transition to modern event handling.

thats because your webpack config isnt set to understand how to handle javascript files, add this to your rules key:
{
test: /\.jsx?/,
loader: 'babel-loader'
}
you will also need to install babel loader that is in a version compatible with your babel core, for v7^ you need to install #babel/core #babel/loader etc, otherwise, babel-loader etc

Related

Webpack: How do I get functions working globally?

For the past 3-4 years, I've been using gulp to build a production JS file from a series of disparate JS files. I'm not using React or any other libraries, and my only assets are JS files written entirely in vanilla JS. With the gulp-built file, I'm able to call functions throughout my code without any issues - my files can talk to each other, so to speak.
But now, I'm wanting to migrate to from gulp to webpack. And for such a simple use case, I'm running into trouble when it comes to global scope. Whenever I test the bundled file in the browser, I can see undefined errors for functions in my file.
I realize that this outcome is to be expected, as webpack modularizes code a bit differently than gulp to prevent collisions. But surely there's a way to modify my webpack configuration so that it works like gulp has for me?
I've read through posts about similar issues and also reviewed the webpack docs to try and solve this on my own, but no luck.
Here's what I've tried:
Explicitly assigning functions to window.
function foo() {
...
}
window.foo = foo;
While this technically works, it's not a sustainable approach given how many functions are in my code. I don't want to be in the business of manually assigning each function to window.
Changing the value of useBuiltIns: Just for sh*** and giggles, I tried "usage" and "entry" in addition to the default option of "false" -- none of this worked.
Adding node.global: true to my module.exports. This didn't work either. Not to mention, I think this approach is outdated as of webpack 5.
Does anyone have guidance for how to get webpack working the way I need it to? Below is my current config.
webpack.config.js
const path = require('path');
const glob = require('glob');
const apis = glob.sync('./code/api/*.js');
const legacy = glob.sync('./code/legacy/*.js');
const standardTags = glob.sync('./code/standardTags/*.js');
const endpoints = glob.sync('./code/core/endpoints/*.js');
const auxFunctions = glob.sync('./code/aux-functions/*.js');
const sourceArray = [...apis, ...legacy, ...standardTags, ...endpoints, ...auxFunctions];
const presets = [
[
"#babel/preset-env",
{
"corejs": {
"version":3
},
"useBuiltIns": "usage",
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
"ie": "9"
}
}
]
];
module.exports = {
entry: [...sourceFileArray],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/nep-build.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets
}
}
}
]
},
};

How to call imported jquery functions in dev tools console? [duplicate]

I want to expose the jQuery object to the global window object that is accessible inside the developer console in the browser. Now in my webpack config I have following lines:
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
These lines add the jQuery definitions to each file in my webpack modules.
But when I build the project and try to access jQuery in the developer console like this:
window.$;
window.jQuery;
it says that these properties are undefined...
Is there a way to fix this?
You need to use the expose-loader.
npm install expose-loader --save-dev
You can either do this when you require it:
require("expose?$!jquery");
or you can do this in your config:
loaders: [
{ test: require.resolve('jquery'), loader: 'expose?jQuery!expose?$' }
]
UPDATE: As of webpack 2, you need to use expose-loader instead of expose:
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: '$'
}]
}]
}
The ProvidePlugin replaces a symbol in another source through the respective import, but does not expose the symbol on the global namespace.
A classic example are jQuery plugins. Most of them just expect jQuery to be defined globally. With the ProvidePlugin you would make sure that jQuery is a dependency (e.g. loaded before) and the occurence of jQuery in their code would be replaced with the webpack raw equivalent of require('jquery').
If you have external scripts relying on the symbol to be in the global namespace (like let's say an externally hosted JS, Javascript calls in Selenium or simply accessing the symbol in the browser's console) you want to use the expose-loader instead.
In short: ProvidePlugin manages build-time dependencies to global symbols whereas the expose-loader manages runtime dependencies to global symbols.
Looks like the window object is exposed in all modules.
Why not just import/require JQuery and put:
window.$ = window.JQuery = JQuery;
You will need to ensure that this happens before requiring/importing any module that makes use of window.JQuery, either in a requiring module or in the module where it's being used.
This always worked for me. including for webpack 3 window.$ = window.jQuery = require("jquery");
None of the above worked for me. (and I really don't like the expose-loader syntax). Instead,
I added to webpack.config.js:
var webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
})
]
}
Than all modules have access through jQuery through $.
You can expose it to the window by adding the following to any of your modules bundled by webpack:
window.$ = window.jQuery = $
Update for Webpack v2
Install expose-loader as described by Matt Derrick:
npm install expose-loader --save-dev
Then insert the following snippet in your webpack.config.js:
module.exports = {
entry: {
// ...
},
output: {
// ...
},
module: {
loaders: [
{ test: require.resolve("jquery"), loader: "expose-loader?$!expose-loader?jQuery" }
]
}
};
(from the expose-loader docs)
In my case works
{ test: require.resolve("jquery"), loader: "expose?$!expose?jQuery" }
Update for Webpack v2
After webpack 5 upgrade, you could face this warning.
[DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING] DeprecationWarning: Using a string as loader options is deprecated (ruleSet[1].rules[7].use[0].options)
Simply change the options to
options: {
exposes: ["$", "jQuery"],
}
will look like this:
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
{
exposes: ["$", "jQuery"],
}
}]
}]
}

Bundle Wasm + JS file into one using webpack

I am currently using Emscripten to compile our C++ Code into Wasm. By doing so I output two files *.js and *.wasm. Later I use our implementation to write more Javascript code on top of that which leads us to 3 files:
index.js
wasmFile.js
wasmFile.wasm
I am trying to use webpack to create a single file that will package everything at build time rather than runtime with this piece of code:
function loadScript(url = "wasmFile.js") {
var script = document.createElement( "script" );
script.src = url;
document.getElementsByTagName( "head" )[0].appendChild( script );
await new Promise<void>((res) => {
Module.onRuntimeInitialized = () => res();
});
}
I have looked into https://github.com/ballercat/wasm-loader However, it looks like i would need to create a WebAssembly.Instance for all my function - and the Wasm file has a lot of functions to create an instance for each.
This is how our WebPack config looks like at the moment:
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.ts$/,
enforce: 'pre',
loader: 'tslint-loader',
options: {
emitErrors: true
}
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist')
}
};
Is there something we are missing on this? Or another package i could use to accomplish this? Any help would be wonderful.
Thanks!
You can build your app as a single JS file using -s SINGLE_FILE=1
Note: This answer is probably not exactly what you want but it solved similar problem for me.
Try out the --bind option. It will output a js and wasm file, the js file loads wasm files and exports the functions to be used in js.
Embind doc
Emcc doc search for bind

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!

Webpack exclude entries for CommonsChunkPlugin

I am trying to set up webpack production configuration. All looks well. However, I realized that while using the commons chunk plugin, it covers all the files in common as expected. What I want to do is, separation of common library modules and common application modules. My config file is :
module.exports = {
entry: {
lib: ["react", "react-dom"],
app: "./ui-v2/app/app.js",
app2: "./ui-v2/app/app2.js"
},
output: {
path: path.join(__dirname, "target/ui/v2"),
filename: "/app/[name].[chunkhash].min.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.(png|jpg|svg)/,
loader: "file-loader?name=img/[name].[hash].[ext]"
// loaders: ["url", "image-webpack"]
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!sass-loader", {
publicPath: __dirname
})
},
{
test: /\.(woff|woff2|ttf|eot)$/,
loader: "file-loader?name=fonts/[name].[hash].[ext]"
}
]
},
plugins: [
clean,
new webpack.optimize.CommonsChunkPlugin("common", "app/common.[chunkhash].js"),
new webpack.ProvidePlugin({
React: "react",
ReactDOM: "react-dom",
$: "jquery",
_: "lodash"
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
sourceMap: true
},
mangle: {
except: ["exports", "import", "$", "_", "require", "React", "ReactDOM"]
}
}),
new ExtractTextPlugin("styles/[name].[contenthash].css"),
new Manifest()
]
}
Basically I have 3 modules in the app; app.js, app2.js and a common component user.js.
What I want to achieve is to bundle all library related files like react, react-dom, lodash, etc in a lib bundle, and common application components like user.js in a common bundle. In order to do this, I thought there might be an option to exclude the files that I don't want them to go to "common" file. If I use this output, what is the point for long term caching files for library bundles because whenever I get a common component in my project, they will go into the common bundle and the content hash will be different, but nothing changes in this library files like react, jquery, lodash, etc.
Anyway, what I have at the end of build process is everything still goes into the common bundle and lib has nothing and the file sizes are :
app.<hash>.min.js -> 3.05KB
app2.<hash>.min.js -> 3.05KB
lib.<hash>.min.js -> 165 Bytes (has almost nothing!)
common.<hash>.js -> 678 KB
Is there any way to achieve what I want or what would be the best approach to a production build in similar cases? Thank you!
Its because the first parameter for CommonsChunkPlugin is "common" where it should be "lib". The plugin picks up the entry with a name matching with the value of its first parameter.
A simple example config picked from webpack's wiki -
var webpack = require("webpack");
module.exports = {
entry: {
app: "./app.js",
vendor: ["jquery", "underscore", ...],
},
output: {
filename: "bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")
]
};
Note that the "vendor" entry is again specified in CommonsChunkPlugin
You should check out Webpack's DLL Plugin.
https://github.com/webpack/webpack/blob/cb3d8e2097503c7245c5dda5b7a6e9d63421a72b/examples/dll/README.md
With this plugin you bundle up common 3rd party vendor dependencies such as React and friends in a DLL, which is essentially just a JSON Manifest that goes along with your requires wrapped in webpack context and cached to disk.
In your project code, you would have your shared components which depend on React and friends, and you would have your application code which depend on your shared components as well as react and friends.
Your project would incorporate the DLL Reference plugin as you can see here:
https://github.com/webpack/webpack/blob/cb3d8e2097503c7245c5dda5b7a6e9d63421a72b/examples/dll-user/README.md
This will see to it that your shared components and your application code pull React and other 3rd party modules from the same DLL bundle. This can help improve build times and the performance of the dev server and hot module reloading.

Categories

Resources