How to properly add jquery plugins to jquery object in webpack? - javascript

TL;DR
What is the proper way of extending jQuery object with plugins, exposing it globally and using external AMD libs with ES6 modules in webpack?
Is webpack the right tool for the task, or would SystemJs suit the situation of refactoring a legacy app to ES6 modules better?
I am trying to wrap my head around working with webpack and ES6 modules. I have a legacy mostly jquery app that I am currently converting. I am facing the following challenges:
finding best practices in the webpack/babel-loader workflow
figuring out which loader/plugin to use for which purpose
getting AMD resources like jquery and jquery plugins to play nice with the rest of the modules.
exposing jquery globals, extended with all the plugins and jquery-ui
I have relied on the following resources:
This great answer explains a lot, though it does not mention the exports loader, which I am mostly relying on: https://stackoverflow.com/a/28989476/2613786
http://webpack.github.io/docs/shimming-modules.html - the documentation lists many possibilities, but i lack the experience to decide which one is the right one. It seems to be preferred to use the ProvidePlugin instead of the expose-loader. Sadly I didn't get it to work with an extended jQuery object. Neither did it work for the use of module functions invoced in <script> tags.
I still struggle to find programatic solutions and decide which webpack plugin is the right one for the job. Some advice or examples from an experienced webpack user are greatly appreciated.
In my webpack.config.js i have the following loaders to expose jquery and transpile with babel:
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {modules: 'common'}
},
{
test: /jquery\.js$/,
exclude: /node_modules/,
loader: 'expose?jQuery',
},
{
test: /jquery\.js$/,
exclude: /node_modules/,
loader: 'expose?$',
},
{
test: /[\/\\]vendor[\/\\]jquery.sparkline\.js$/,
loader: "imports?define=>false"
}
]
},
amd: { jQuery: true },
// plugins: [
// new webpack.ProvidePlugin({
// $: 'jquery',
// jQuery: 'jquery',
// 'window.jQuery': 'jquery',
// 'root.jQuery': 'jquery'
// })
// ], ...
In my entry.js file I include jquery in the following way:
import 'expose?jQuery!expose?$!./vendor/jquery';
import './jquery/jquery-ui';
import './vendor/jquery.sparkline';
I had to comment out the ProvidePlugin, when i use it, the jQuery is not extended with custom plugins anymore, any idea why that is the case?
Does it have to do with the plugins using ES6 module syntax?
I had to add loader: "imports?define=>false" for jquery.sparkline.js to get it to be recogniced. Is this really necessary, or is there a better way to do it?
Concerning jquery-ui i had to find an old version that did not use AMD define to get it to add to the jquery object. What would be the right way to do it?
Any help and advice is greatly appreciated, a reason to switch to SystemJs and Jspm might also be a solution.

I have had issues with this and it seems that it was because some plugins assume $ and others assume jQuery, but the following eventually worked for me, even if it is rather ugly:
Edit, note that I'm testing for plugins which are named jquery.xyz.js, you'd have to adjust the regex appropriately.
Also, I'm not sure if the two different expose loaders for jQuery are causing issues, but so far this works.
// webpack.config.js
...
"module": {
"loaders": [
{
test: require.resolve("jquery"),
loader: "expose?$!expose?jQuery"
},
{
test: /jquery\..*\.js/,
loader: "imports?$=jquery,jQuery=jquery,this=>window"
}
...

Related

How to correctly expose jQuery with Webpack 5?

Is there any specific way to expose jQuery with Webpack 5?
It used to work on Webpack 4 OK, with the config bellow, but it shows the Uncaught Reference Error: jQuery is not defined error now with 5.
module: {
rules: [
{
test: require.resolve('jquery'),
loader: 'expose-loader',
options: {
exposes: ['$', 'jQuery'],
},
},
Confirming #Sam Chen 's comment above, Webpack 5 (as of 5.75) does not require expose-loader to make $ available as an import. It was simplest to uninstall the loader, remove it from webpack.config.js, and add
import $ from 'jquery';
to all files that used it. This can get messy in the case of TypeScript projects with #types/jquery, as pages where the import is missing may not warn about it.

How to use web worker on a npm module

I'm writing a JavaScript library and I'm using a web worker. I'm using webpack (with worker-loader) to create my build.
Everything is working fine on the library.
webpack.config.js
{
test: /app.worker.ts$/,
include: [
path.resolve(__dirname, 'src/')
],
use: [
{
loader: 'babel-loader',
},
{
loader: 'worker-loader',
options: {
name: 'app.worker.js',
}
}
]
}
That generate :
/dist/app.bundle.js // my main library build
/dist/app.worker.js // my worker build
When I try to import my library on a react-application, my library try yo load the worker like this:
http://localhost:3000/dist/app.worker.js.
which obviously fail because it's located in node_modules/.../dist/app.worker.js.
I probably don't use worker-loader correctly.
Thanks for your help
ok, I just found the problem.
I don't know why but the issue is related to yarn link. If I replace the link: with file: in my package.json, I don't have the problem anymore.
Another (non optimal) solution is to use inline:true.
Anyone know a solution or a workaround with yarn link ?
Thanks !

Set up Webpack and Babel to transpile / polyfill older code

I have an entire legacy AngularJS 1.x application that used to run through gulp and babel. We are transitioning to the newer Angular 2+ but I'm running into an issue trying to get Webpack to actually find and compile the legacy files. I've tried following instructions which are all almost identical like this video:
https://www.youtube.com/watch?v=H_QACBSqRBE
But the webpack config simply doesn't do anything to the existing files. Is there a way to grab a WHOLE FOLDER of older components, that DO NOT have any imports or exports? I feel like entry is supposed to follow the import dependency path but that just doesn't exist for older AngularJS 1.x projects.
Errors are NOT being thrown during the build it just...doesn't transpile or polyfill.
example of what that section of the config looks like:
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
}
]
We did this recently. We created "dummy" entry point files to avoid having to change all of our AngularJS files to have require/import statements.
./entry-points/feature1.ts
export const importAll = (r: any): void => {
r.keys().forEach(r);
};
importAll(require.context('./app/feature1', true, /module\.js$/));
importAll(require.context('./app/feature1', true, /(^(?!.*(spec|module)\.js).*\.js)$/));
webpack.config.js
entry: {
'feature1': './entry-points/feature1.ts'
}
More detail here

Define css files within webpack.config.js

I am building something like a static website generator that uses webpack to build the project and create a bundle with it.
In this project, a user is able to specify custom css files. I want those css files to be bundled with the final result. The issue is, that I do not have the paths to those css files available during development, so I can't do import 'some-asset-file-provided-by-the-user.css' in the javascript code that is going to be bundled. But I have them available when calling webpack.compile(config).
I am looking for a way to inject those css files into the bundle. So far I tried various ways, such as:
const stylesheet = 'some-asset-file-provided-by-the-user.css'
require(stylesheet)
Which did not work, probably because webpack is not able to deal with this "dynamic" require. Then I used the webpack define plugin for this
/* webpack.config.js */
new webpack.DefinePlugin({
stylesheet: 'some-asset-file-provided-by-the-user.css'
}),
/* app.js */
require(stylesheet) // should be replaced by the webpack define plugin with 'some-asset-file-provided-by-the-user.css'
which also did not work. I also tried to find a way to do something like this:
{
test: /\.css$/,
loader: ExtractTextPlugin.extract(Object.assign({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
useFiles: ['file-a.css', 'file-b.css']
}
}
]
}, extractTextPluginOptions))
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
},
which also failed because apparently neither style-loader nor css-loader support this type of interaction.
How can I solve this? I am open to writing a plugin for this, but I'd rather use something existing.
The simplest way to include the CSS is by adding it to your entry point. To make this easier, you should use an array as entry point even if it's just a single file, so you can simply push the CSS.
For example:
entry: {
app: ['./src/index.js'],
// Other entries
},
In your compile script you add it to entry.app before passing it to webpack.
config.entry.app.push('./user.css');
const compiler = webpack(config);

Load requirejs module's css dependency with webpack

I am using webpack and I need to include some libraries built for requirejs.
Everything worked fine until one of the library declared a css dependency:
define(["css!./stylesheet.css"], function(){ \* module *\ });
Webpack has a css loader too, however it does not load them automatically as requirejs's one does. One must pipe the css loader to the style loader to do so:
require("style!css!./stylesheet.css");
Is there any way to make the prior working? For example, can I overwrite the css loader for this particular library so that it is piped with the style loader ?
I found a nice solution using postLoaders.
Adding
postLoaders: [
{ test: /\.css$/, loader: 'style', include: path.join(__dirname, "pathTo/theLib") }
]
into the module property of the webpack config did the trick.

Categories