Initializing third-part plugin in webpack bundle.js - javascript

One of my module is external plugin (WOW effect), which needs to be initialize in index.html to works properly by using:
<script>
new WOW().init();
</script>
If I use the plugin as a completely external file - it works. But when I compile it with webpack it gives me an error:
Uncaught ReferenceError: wow is not defined.
My config file looks like this:
module.exports = {
entry: './modules/_entry.js',
output: {
filename: 'bundle.js'
}
};
and _entry.js contains:
require("./wow.js");
What I am doing wrong?

There are two possibilities:
Externals The benefit of this one is that you can easly exchange your local script file for example for a CDN one. In this method, the source of the script is an external vendor file.
Have a folder with you vendor scripts, like WOW.
Link to it in your HTML file using <script src="vendors/WOW.js">.
You are free to make the initialization like you indicated above, inside another <script> tag.
Add externals configuration to your webpack config:
var config = {
entry: [...],
output: {...},
externals: {
Wow: 'WOW'
},
module: {...},
plugins: [...]
};
From now on, you have access to an artificial module Wow in all your application modules and you can import or require them (although in case of WOW, I don't think you would need it).
Expose-loader This way lets you move to the global scope modules that you would normally use as... well, modules :) In this method, the source of the script is a module (either yours own, or installed with npm).
Install the plugin: npm install expose-loader --save-dev
Add the expose loader configuration to your webpack config file:
var config = {
entry: [...],
output: {...},
module: {
loaders: [{
...
}, {
test: 'path/to/your/module/wow.min.js',
loader: "expose?WOW"
}]
},
plugins: [...]
};
This should make the WOW a global variable, usable inside <script> tags.

Related

Public file is missing from build/public in Webpack [duplicate]

I'm trying to move from Gulp to Webpack. In Gulp I have task which copies all files and folders from /static/ folder to /build/ folder. How to do the same with Webpack? Do I need some plugin?
Requiring assets using the file-loader module is the way webpack is intended to be used (source). However, if you need greater flexibility or want a cleaner interface, you can also copy static files directly using my copy-webpack-plugin (npm, Github). For your static to build example:
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
context: path.join(__dirname, 'your-app'),
plugins: [
new CopyWebpackPlugin({
patterns: [
{ from: 'static' }
]
})
]
};
Compatibility note: If you're using an old version of webpack like webpack#4.x.x, use copy-webpack-plugin#6.x.x. Otherwise use latest.
You don't need to copy things around, webpack works different than gulp. Webpack is a module bundler and everything you reference in your files will be included. You just need to specify a loader for that.
So if you write:
var myImage = require("./static/myImage.jpg");
Webpack will first try to parse the referenced file as JavaScript (because that's the default). Of course, that will fail. That's why you need to specify a loader for that file type. The file- or url-loader for instance take the referenced file, put it into webpack's output folder (which should be build in your case) and return the hashed url for that file.
var myImage = require("./static/myImage.jpg");
console.log(myImage); // '/build/12as7f9asfasgasg.jpg'
Usually loaders are applied via the webpack config:
// webpack.config.js
module.exports = {
...
module: {
loaders: [
{ test: /\.(jpe?g|gif|png|svg|woff|ttf|wav|mp3)$/, loader: "file" }
]
}
};
Of course you need to install the file-loader first to make this work.
If you want to copy your static files you can use the file-loader in this way :
for html files :
in webpack.config.js :
module.exports = {
...
module: {
loaders: [
{ test: /\.(html)$/,
loader: "file?name=[path][name].[ext]&context=./app/static"
}
]
}
};
in your js file :
require.context("./static/", true, /^\.\/.*\.html/);
./static/ is relative to where your js file is.
You can do the same with images or whatever.
The context is a powerful method to explore !!
One advantage that the aforementioned copy-webpack-plugin brings that hasn't been explained before is that all the other methods mentioned here still bundle the resources into your bundle files (and require you to "require" or "import" them somewhere). If I just want to move some images around or some template partials, I don't want to clutter up my javascript bundle file with useless references to them, I just want the files emitted in the right place. I haven't found any other way to do this in webpack. Admittedly it's not what webpack originally was designed for, but it's definitely a current use case.
(#BreakDS I hope this answers your question - it's only a benefit if you want it)
Webpack 5 adds Asset Modules which are essentially replacements for common file loaders. I've copied a relevant portion of the documentation below:
asset/resource emits a separate file and exports the URL. Previously achievable by using file-loader.
asset/inline exports a data URI of the asset. Previously achievable by using url-loader.
asset/source exports the source code of the asset. Previously achievable by using raw-loader.
asset automatically chooses between exporting a data URI and emitting a separate file. Previously achievable by using url-loader with asset size limit.
To add one in you can make your config look like so:
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(jpe?g|gif|png|svg|woff|ttf|wav|mp3)$/,
type: "asset/resource"
}
]
}
};
To control how the files get output, you can use templated paths.
In the config you can set the global template here:
// webpack.config.js
module.exports = {
...
output: {
...
assetModuleFilename: '[path][name].[hash][ext][query]'
}
}
To override for a specific set of assets, you can do this:
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(jpe?g|gif|png|svg|woff|ttf|wav|mp3)$/,
type: "asset/resource"
generator: {
filename: '[path][name].[hash][ext][query]'
}
}
]
}
};
The provided templating will result in filenames that look like build/images/img.151cfcfa1bd74779aadb.png. The hash can be useful for cache busting etc. You should modify to your needs.
Above suggestions are good. But to try to answer your question directly I'd suggest using cpy-cli in a script defined in your package.json.
This example expects node to somewhere on your path. Install cpy-cli as a development dependency:
npm install --save-dev cpy-cli
Then create a couple of nodejs files. One to do the copy and the other to display a checkmark and message.
copy.js
#!/usr/bin/env node
var shelljs = require('shelljs');
var addCheckMark = require('./helpers/checkmark');
var path = require('path');
var cpy = path.join(__dirname, '../node_modules/cpy-cli/cli.js');
shelljs.exec(cpy + ' /static/* /build/', addCheckMark.bind(null, callback));
function callback() {
process.stdout.write(' Copied /static/* to the /build/ directory\n\n');
}
checkmark.js
var chalk = require('chalk');
/**
* Adds mark check symbol
*/
function addCheckMark(callback) {
process.stdout.write(chalk.green(' ✓'));
callback();
}
module.exports = addCheckMark;
Add the script in package.json. Assuming scripts are in <project-root>/scripts/
...
"scripts": {
"copy": "node scripts/copy.js",
...
To run the sript:
npm run copy
The way I load static images and fonts:
module: {
rules: [
....
{
test: /\.(jpe?g|png|gif|svg)$/i,
/* Exclude fonts while working with images, e.g. .svg can be both image or font. */
exclude: path.resolve(__dirname, '../src/assets/fonts'),
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
}]
},
{
test: /\.(woff(2)?|ttf|eot|svg|otf)(\?v=\d+\.\d+\.\d+)?$/,
/* Exclude images while working with fonts, e.g. .svg can be both image or font. */
exclude: path.resolve(__dirname, '../src/assets/images'),
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
},
}
]
}
Don't forget to install file-loader to have that working.
You can write bash in your package.json:
# package.json
{
"name": ...,
"version": ...,
"scripts": {
"build": "NODE_ENV=production npm run webpack && cp -v <this> <that> && echo ok",
...
}
}
Most likely you should use CopyWebpackPlugin which was mentioned in kevlened answer. Alternativly for some kind of files like .html or .json you can also use raw-loader or json-loader. Install it via npm install -D raw-loader and then what you only need to do is to add another loader to our webpack.config.js file.
Like:
{
test: /\.html/,
loader: 'raw'
}
Note: Restart the webpack-dev-server for any config changes to take effect.
And now you can require html files using relative paths, this makes it much easier to move folders around.
template: require('./nav.html')
I was stuck here too. copy-webpack-plugin worked for me.
However, 'copy-webpack-plugin' was not necessary in my case (i learned later).
webpack ignores root paths
example
<img src="/images/logo.png'>
Hence, to make this work without using 'copy-webpack-plugin'
use '~' in paths
<img src="~images/logo.png'>
'~' tells webpack to consider 'images' as a module
note:
you might have to add the parent directory of images directory in
resolve: {
modules: [
'parent-directory of images',
'node_modules'
]
}
Visit https://vuejs-templates.github.io/webpack/static.html
The webpack config file (in webpack 2) allows you to export a promise chain, so long as the last step returns a webpack config object. See promise configuration docs. From there:
webpack now supports returning a Promise from the configuration file. This allows to do async processing in you configuration file.
You could create a simple recursive copy function that copies your file, and only after that triggers webpack. E.g.:
module.exports = function(){
return copyTheFiles( inpath, outpath).then( result => {
return { entry: "..." } // Etc etc
} )
}
lets say all your static assets are in a folder "static" at the root level and you want copy them to the build folder maintaining the structure of subfolder, then
in your entry file) just put
//index.js or index.jsx
require.context("!!file?name=[path][name].[ext]&context=./static!../static/", true, /^\.\/.*\.*/);
In my case I used webpack for a wordpress plugin to compress js files, where the plugin files are already compressed and need to skip from the process.
optimization: {
minimize: false,
},
externals: {
"jquery": "jQuery",
},
entry: glob.sync('./js/plugin/**.js').reduce(function (obj, el) {
obj[path.parse(el).name] = el;
return obj
}, {}),
output: {
path: path.resolve(__dirname, './js/dist/plugin'),
filename: "[name].js",
clean: true,
},
That used to copy the js file as it is to the build folder. Using any other methods like file-loader and copy-webpack create issues with that.
Hope it will help someone.

How to inject Webpack DefinePlugin variables in non Module Scripts

I have the following issue:
I'm using the DefinePlugin to define some variables across .js modules. This is working fine in .js modules
However, my multi page application loads a local script (custom.js) inside a <HEADER> TAG. This script is standard javascript (not a module), using one of the variables defined in DefinePlugin. It's a .js that must be loaded in every page of the App.
For some reason this variable WEB_CONTEXT is not being interpolated by Webpack on BUILD process.
I assume that the reason is that as it is not recognized as a dependency.
Webpack config.js:
new webpack.DefinePlugin({
'WEB_CONTEXT': 'myapp/main'
);
The global script is loaded like this:
<script src="./src/js/custom.js"></script>
custom.js
$(function () {
// Compiled file incorrectly shows:
const myPath = `${WEB_CONTEXT}/resources/images`;
// Instead of:
const myPath = `myapp/main/resources/images`;
});
});
Question is:
Is there anyway with Webpack to make WEB_CONTEXT variable available ALSO for those scripts like custom.js (not imported, but loaded via ?
Webpack "works" only on files that are part of the dependency tree that starts from the entry file.
If your custom.js file is not inside this tree, webpack won't touch it.
You can add it by require it, or add it as additional entry to your app.
//webpack.config.js
module.exports = {
entry: {
main: './path/to/my/entry/file.js',
custom: './path/to/custom.js'
}
};
webpack.config.js
entry : {
main: './path/to/my/entry/file.js',
custom: './path/to/custom.js'
}
And the in the plugins section:
new HtmlWebpackPlugin({
template: `./${ruta}`,
inject: true,
chunks: ['main', 'custom'], // <-Added custom chunk here.
filename: `${fileName}.html`,
templateParameters: {
WEB_CONTEXT: basePath
}
});

How to change the src attribute of <img> in .vue files with webpack and vue-loader?

I have a vue.js (version 2.4.4) application built with webpack (version 3.6.0) and vue-loader (version 13.0.5).
In the .vue files, I need to modify the url contained in the src attribute of the <img> tags according to the environment of my application.
In the development environment, the images must come from the
application folder, with a relative path: "/src/images/exemple.png"
In the production environment, the images must come from a cdn, with
an absolute path: "https://my-cdn.net/images/exemple.png"
In the "webpack.config.js" file, I already differentiate the different environments using "process.env.NODE_ENV", like this:
const isDev = process.env.NODE_ENV === 'dev';
But I don't know how to modify the src attribute of the img tags in my .vue files with vue-loader (or with something else).
For information, here is the definition of the vue-loader in the "webpack.config.js" file:
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
'scss': [
'vue-style-loader',
'css-loader',
'sass-loader'
]
}
}
}
Is there a simple way to do this?
Piggybacking off of #Michael Levy's answer:
I am currently having this issue with Vue 3, #vue/cli 4.5.10, and webpack. I've solved it after much research.
Webpack configurations go into vue.config.js, where there is a lot of abstraction. To fine tune control, you can use chain webpack configs. To help you, use Vue Inspect when you are trying to access specific loaders via chaining.
$ vue inspect > output.js
That will give you a nice list of all the loaders that vue-cli is using.
For example - to modify webpack image options within vue.config.js, you can use vue inspect > output.js, search the output.js file, and discover the loader that's managing images.
Which is: /* config.module.rule('images').use('url-loader') */
To answer the question - in your vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule("images")
.use("url-loader")
.tap((options) => {
options.name = "images/[name].[ext]";
options.publicPath = isDev ? __webpack_public_path__ : 'https://my-cdn.net/';
return options;
});
},
};
Vue-loader is preconfigured to handle src attributes in Vue single file components as you can see here. So for example <img src="../image.png"> in the template is transformed into:
createElement('img', {
attrs: {
src: require('../image.png') // this is now a module request
}
})
What Webpack do with this require depends on configured loaders. Usual there is a file-loader configured. It looks like this (from project generated by Vue CLI + simplified):
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
{
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
]
}
]
}
Loader is responsible for copying your file into dist directory and returning public URI, which will be inserted into src attribute.
So what you want can be configured here, by specifying right options. For example:
options: {
name: 'images/[name].[ext]'
publicPath: isDev ? __webpack_public_path__ : 'https://my-cdn.net/'
}
Just take content of dist/images directory after the build and deploy it so it is accessible by https://my-cdn.net/images and it should work....
You can create an alias for/src/images and alter the url at transpile time based on the environment:
{
//..
resolve: {
alias: {
'/src/images': isDev ? '/src/images' : process.env.IMAGE_CDN + '/images'
}
}
}
Another way to handle this would be to use DefinePlugin to create a global variable that each of your images reference.
module.exports = {
chainWebpack: config => {
console.log('\n')
console.log('Setting global variables:')
console.log(`__YOUR_GLOBAL_CONSTANT__: ${JSON.stringify(process.env.YOUR_GLOBAL_CONSTANT)}`)
console.log('\n')
config
.plugin('provide')
.use(require('webpack').DefinePlugin, [{
__YOUR_GLOBAL_CONSTANT__: JSON.stringify(process.env.YOUR_GLOBAL_CONSTANT)
}])
}
}
The example above is utilizing a vue.config.js file, but the strategy should be pretty similar. Also, if you're using something like eslint, you'll need to specify the variable in the globals section as readonly.

Using expose-loader with webpack 2 to load a module

I have jwplayer in my lib/ folder because no node_module exists. I tried to use expose-loader in order to be able to import it. In my webpack, I have the following basic setup in order to get this to work:
const path = require('path');
module.exports = {
// ...
module: {
rules: [{
test: /\.jwplayer\.js$/,
use: {
loader: 'expose-loader',
options: 'jwplayer', // have also tried `options: { name: 'jwplayer' }`
},
}]
},
resolve: {
alias: {
jwplayer: path.join(__dirname, './lib/jwplayer-7.7.4/jwplayer.js'),
}
},
externals: {
window: 'Window',
}
};
The strange thing is, jwplayer is exposed on the window object, but it is not available as a module.
import jwplayer from 'jwplayer';
console.log(jwplayer); // Object {} (not jwplayer)
console.log(window.jwplayer); // function jwplayer() {}
Am I loading it incorrectly? How should I load in jwplayer with webpack 2?
That's not how you use the expose loader. The expose loader tells to webpack to expose something to the global context when the bundle is loaded. My understanding is that you want to use jwplayer inside the bundle itself.
You can use the script-loader, that's how I usually import scripts (analytics, for instance)
Actually you can use
externals: ['jwplayer'],
Because externals is for passing global variables inside the bundle to be able to use them as a dependency and then you can import your library as any other
import jwplayer from 'jwplayer';
webpack documentation

Simple solution to share modules loaded via NPM across multiple Browserify or Webpack bundles

Pulling my hair out here looking for a simple solution to share code, required via NPM, across multiple Browserify or Webpack bundles. Thinking, is there such a thing as a file "bridge"?
This isn't due to compile time (I'm aware of watchify) but rather the desire to extract out all of my vendor specific libs into vendor.js so to keep my app.js filesize down and to not crash the browser with massive sourcemaps. Plus, I find it way cleaner should the need to view the compiled js arise. And so:
// vendor.js
require('react');
require('lodash');
require('other-npm-module');
require('another-npm-module');
Its very important that the code be loaded from NPM as opposed to Bower, or saved into some 'vendor' directory in order to be imported via a relative path and identified via a shim. I'd like to keep every library reference pulled via NPM except for my actual application source.
In app.js I keep all of my sourcecode, and via the externals array, exclude vendor libraries listed above from compilation:
// app.js
var React = require('react');
var _ = require('lodash');
var Component = React.createClass()
// ...
And then in index.html, I require both files
// index.html
<script src='vendor.js'></script>
<script src='app.js'></script>
Using Browserify or Webpack, how can I make it so that app.js can "see" into those module loaded via npm? I'm aware of creating a bundle with externals and then referencing the direct file (in, say, node_modules) via an alias, but I'm hoping to find a solution that is more automatic and less "Require.js" like.
Basically, I'm wondering if it is possible to bridge the two so that app.js can look inside vendor.js in order to resolve dependencies. This seems like a simple, straightforward operation but I can't seem to find an answer anywhere on this wide, wide web.
Thanks!
Listing all the vendor files/modules and using CommonChunkPlugin is indeed the recommended way. This gets pretty tedious though, and error prone.
Consider these NPM modules: fastclick and mprogress. Since they have not adopted the CommonJS module format, you need to give webpack a hand, like this:
require('imports?define=>false!fastclick')(document.body);
require('mprogress/mprogress.min.css');
var Mprogress = require('mprogress/mprogress.min.js'),
Now assuming you would want both fastclick and mprogress in your vendor chunk, you would probably try this:
module.exports = {
entry: {
app: "./app.js",
vendor: ["fastclick", "mprogress", ...]
Alas, it doesn't work. You need to match the calls to require():
module.exports = {
entry: {
app: "./app.js",
vendor: [
"imports?define=>false!fastclick",
"mprogress/mprogress.min.css",
"mprogress/mprogress.min.js",
...]
It gets old, even with some resolve.alias trickery. Here is my workaround. CommonChunkPlugin lets you specify a callback that will return whether or not you want a module to be included in the vendor chunk. If your own source code is in a specific src directory, and the rest is in the node_modules directory, just reject the modules based on their path:
var node_modules_dir = path.join(__dirname, 'node_modules'),
app_dir = path.join(__dirname, 'src');
module.exports = {
entry: {
app: "./app.js",
},
output: {
filename: "bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(
/* chunkName= */"vendor",
/* filename= */"vendor.bundle.js"
function (module, count) {
return module.resource && module.resource.indexOf(app_dir) === -1;
}
)
]
};
Where module.resource is the path to the module being considered. You could also do the opposite, and include only the module if it is inside node_modules_dir, i.e.:
return module.resource && module.resource.indexOf(node_modules_dir) === 0;
but in my situation, I'd rather say: "put everything that is not in my source source tree in a vendor chunk".
Hope that helps.
With webpack you'd use multiple entry points and the CommonChunkPlugin.
Taken from the webpack docs:
To split your app into 2 files, say app.js and vendor.js, you can require the vendor files in vendor.js. Then pass this name to the CommonChunkPlugin as shown below.
module.exports = {
entry: {
app: "./app.js",
vendor: ["jquery", "underscore", ...],
},
output: {
filename: "bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(
/* chunkName= */"vendor",
/* filename= */"vendor.bundle.js"
)
]
};
This will remove all modules in the vendor chunk from the app chunk. The bundle.js will now contain just your app code, without any of it’s dependencies. These are in vendor.bundle.js.
In your HTML page load vendor.bundle.js before bundle.js.
<script src="vendor.bundle.js"></script>
<script src="bundle.js"></script>
// vendor anything coming from node_modules
minChunks: module => /node_modules/.test(module.resource)
Source: https://github.com/webpack/webpack/issues/2372#issuecomment-213149173

Categories

Resources