Using modules downloaded from npm - javascript

i have been reading this guide on react where the following paragraph has kind of thrown me.
For a number of good technical reasons CommonJS modules (i.e.
everything in npm) cannot be used natively in the browser. You need a
JavaScript “bundler” to “bundle” these modules into .js files that you
can include in your web page with a tag.
I was under the impression that i could do something like npm install jquery and then in my index.html file reference this through node modules, something like
<script src="node_modules/jquery/dist.jquery.js">
Is this not the case, or am I incorrect?

Yes. You are correct. Jquery is a global module not a commonjs module. For other package that use commonjs module, using import/export statement you need a bundler like browserify to bundle it into a single bundle in order to use it in browser

As mentioned in the guide you read, examples for those "bundlers" are webpack/browserify/systemjs/etc..
These are what is called as "module-loaders" which basically loads the modules to the browser when running your applications.
These module-loaders have a configuration file.
So if for example yours is webpack, after you install npm install webpack you'll need to have a webpack.config.js that might look as follows:
module.exports = {
entry: "./app/boot",
output: {
path: __dirname,
filename: "./dist/bundle.js"
},
resolve: {
extensions: ['', '.js', '.ts']
},
module: {
loaders: [
{ test: /\.ts/, loader: ["ts-loader"], exclude: /node_modules/ },
],
preLoaders: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: "source-map-loader", exclude: ['node_modules', 'ext-*', 'lib', 'tools'] }
]
},
debug: true,
devtool: 'source-map'
};

When the jquery library is loaded, it verifies if it is being imported by a bundler/loader or by a script tag:
( function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global ); // export globally
}
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { /** jQuery definition **/ }

Related

Webpack does not import bundles from node_modules (js only)

Good Morning,
I'm quite new to Webpack and feel a bit lost - importing modules from the source path works just fine - but importing modules from node_modules (e.g. jQuery) gives me error messages that the module is not found. I am completely lost and don't even know what to look for or how to debug this further.
The error message i am getting is:
external "jquery":1 Uncaught ReferenceError: jquery is not defined
at Object.jquery (external "jquery":1)
at __webpack_require__ (bootstrap:723)
at fn (bootstrap:100)
at Object../js/ManagementApplication.ts (ManagementApplication.ts:5)
at __webpack_require__ (bootstrap:723)
at fn (bootstrap:100)
at Object.0 (dist.js:40457)
at __webpack_require__ (bootstrap:723)
at bootstrap:790
at bootstrap:790
jquery # external "jquery":1
__webpack_require__ # bootstrap:723
fn # bootstrap:100
./js/ManagementApplication.ts # ManagementApplication.ts:5
__webpack_require__ # bootstrap:723
fn # bootstrap:100
0 # dist.js:40457
__webpack_require__ # bootstrap:723
(anonymous) # bootstrap:790
(anonymous) # bootstrap:790
and here is my webpack config:
// shared config (dev and prod)
const {resolve} = require('path');
const {CheckerPlugin} = require('awesome-typescript-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require("webpack")
module.exports = {
resolve: {
extensions: ['.ts', '.js'],
},
context: resolve(__dirname, '../src/main/'),
output: {
filename: "dist.js",
path: resolve(__dirname, '../target')
},
externals: {
bootstrap: "bootstrap",
jquery: "jquery"
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
},
{
test: /\.tsx?$/,
use: ['babel-loader', 'awesome-typescript-loader'],
},
{
test: /\.css$/,
use: ['style-loader', {loader: 'css-loader', options: {importLoaders: 1}}],
},
{
test: /\.(scss)$/,
use: [{
loader: 'style-loader', // inject CSS to page
}, {
loader: 'css-loader', // translates CSS into CommonJS modules
}, {
loader: 'postcss-loader', // Run postcss actions
options: {
plugins: function () { // postcss plugins, can be exported to postcss.config.js
return [
require('autoprefixer')
];
}
}
}, {
loader: 'sass-loader' // compiles Sass to CSS
}]
},
{
test: /\.woff2?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: 'url-loader?limit=10000',
},
{
test: /\.hbs/,
loaders: "handlebars-loader"
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loaders: [
'file-loader?hash=sha512&digest=hex&name=img/[hash].[ext]',
'image-webpack-loader?bypassOnDebug&optipng.optimizationLevel=7&gifsicle.interlaced=false',
],
},
],
},
plugins: [
new CheckerPlugin(),
new HtmlWebpackPlugin(),
new webpack.IgnorePlugin(/\/iconv-loader$/)
],
performance: {
hints: false,
},
};
and this one:
// development config
const merge = require('webpack-merge');
const webpack = require('webpack');
const commonConfig = require('./common');
module.exports = merge(commonConfig, {
mode: 'development',
entry: [
'webpack-dev-server/client?http://localhost:4000',// bundle the client for webpack-dev-server and connect to the provided endpoint
'webpack/hot/only-dev-server', // bundle the client for hot reloading, only- means to only hot reload for successful updates
'./js/ManagementApplication.ts' // the entry point of our app
],
devServer: {
hot: true,
host: "0.0.0.0",
port: "4000"
},
devtool: 'source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(), // enable HMR globally
new webpack.NamedModulesPlugin(), // prints more readable module names in the browser console on HMR updates
],
});
(both of them are loaded, the latter one overriding the first one).
I've checked a billion times that the libraries are correctly inside node_modules - just don't know why they are not loaded. This problem is not specific only to a specific library but genreally to all libraries.
Importing css resources from libraries works fine in contrast.
Does anyone have an idea how to fix this or can help me understanding what is happening?
If you intended jquery to be treated as an external, #Pandelis answer's right (note the uppercase Q: jquery: jQuery). But in case you want to import jquery as a node module, see below.
Use jQuery as a node module
If you want to use jQuery as a node module & have it bundled, you should install jquery from npm
npm install jquery
Then import it in your code
import $ from "jquery";
No need to add anything to webpack.config.js. But if you want to use jQuery as an external:
Use jQuery as an external
When you do something like this in webpack.config.js:
...
module.exports = {
externals: {
jquery: "jQuery" // or jquery: "$"
},
...
}
It tells webpack that in the line import jquery, jquery shouldn't be bundled; instead, look for the jQuery object in the global scope (which is window in our case). Both jQuery and $ will be valid. It also means you have to load jquery from external source:
#index.html
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<sript src="bundle.js"></script>
Then in your code, you can then do
import 'jquery' // or import $ from 'jquery'
$...
Just to illustrate, you can also do externals: { foo: 'jQuery' } and import 'foo' would still work.
Hope it helps!
Not actually 100% sure without looking at more of the project but give this a go.
Set your jquery external to:
"jquery": "jQuery"
and use jquery in your project as: import jQuery from 'jquery' or import $ from 'jquery'

How to Access jQuery Plugin From A Separate Webpack Bundle

I'm bundling JS and CSS (compiled from SCSS) into two separate bundles, one for 3rd party (vendor) and one for the project code (company). I'm able to access jQuery via $ successfully from scripts in the company bundle as a global, such as from some-other-script.js, without any issues. However when trying to call the stickyTableHeaders function from the StickyTableHeaders plugin in table-headers.js: Uncaught TypeError: $(...).stickyTableHeaders is not a function. I don't get any other errors about loading scripts etc. and I can see that vendor.bundle.js includes the plugin code.
Additionally I see from the bottom of the plugin source that the function is meant to be added to $ as follows:
$.fn[name] = function ( options ) {
return this.each(function () {
var instance = $.data(this, 'plugin_' + name);
if (instance) {
if (typeof options === 'string') {
instance[options].apply(instance);
} else {
instance.updateOptions(options);
}
} else if(options !== 'destroy') {
$.data(this, 'plugin_' + name, new Plugin( this, options ));
}
});
};
Any ideas why it can't find the function on the $ (jQuery) object?
This question seems similar, however the poster was having trouble with the plugin not being able to find jQuery in that case. Additionally I'm not sure if using the import-loader as per one of the suggestions is the right approach in my case, or if I'm doing something fundamentally wrong. You can see commented out lines in the webpack.config.js below where I've tried to register sticky-table-headers as a plugin with webpack without success - same result.
My webpack.config.js is as follows:
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractSass = new ExtractTextPlugin({
filename: "[name].bundle.css",
disable: process.env.NODE_ENV === "development"
});
module.exports = function (env) {
env = env || {};
var isProd = env.NODE_ENV === 'production';
// Setup base config for all environments
var config = {
entry: {
vendor: './Client/js/vendor',
company: './Client/js/company' // Includes all SCSS, which ends up in company.bundle.css via extract-text-webpack-plugin.
},
output: {
// ReSharper disable once UseOfImplicitGlobalInFunctionScope
path: path.join(__dirname, 'wwwroot/dist'),
filename: '[name].bundle.js'
},
devtool: 'eval-source-map',
mode: "development",
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
"jquery.validation": "jquery-validation/dist/jquery.validate.js",
//"sticky-table-headers": "sticky-table-headers/js/jquery.stickytableheaders.js"
}
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
Popper: ['popper.js', 'default'],
//"sticky-table-headers": ["sticky-table-headers", "default"]
}),
extractSass
],
module: {
rules: [
{
test: /\.scss$/,
use: extractSass.extract({
use: [{
loader: "css-loader"
}, {
loader: "sass-loader"
}],
// use style-loader in development
fallback: "style-loader"
})
}
]
}
}
// Alter config for prod environment
if (isProd) {
config.devtool = 'source-map'; // SourceMap emitted as a separate file.
//Normally disallow access on webserver or use (none) instead. Internal
//app so leaving them accessible for easier support.
config.mode = "production";
}
return config;
};
Then in vendor.js I have:
import 'jquery';
import 'popper.js';
import 'bootstrap';
import "jquery-validation";
import "jquery-validation-unobtrusive";
import "sticky-table-headers";
In company.js I have:
import '../scss/site.scss';
import './site';
import './some-other-script';
import './table-headers';
Finally in table-headers.js I have:
(function () {
$(function () {
if ($(".my-sticky-table-header").length === 0) return;
var offset = $('.navbar').height();
$(".my-sticky-table-header").stickyTableHeaders({
fixedOffset:offset});
});
})();
Thanks.
Looks like there was a fundamental flaw with this setup. I ended up adding:
import './vendor';
to the top of company.js and then using the SplitChunks plugin mentioned here to avoid everything in the vendor bundle being duplicated. This allowed library functions to be called from the company bundle.
(Something like this may have worked but it seems messy).

Create npm package using webpack, exports-loader and legacy JavaScript

I am trying to use a legacy JavaScript in a npm package to distribute to other projects.
If I import the Javascript file directly into one of the projects with help of webpack's exports-loader I am able to use all of the functions without any issues.
However if I import the npm package into one of the projects I run into issues. I think my webpack setup in the npm package is incorrectly setup.
npm package:
import { BrowserPrint } from "./BrowserPrint.fat";
exports.printEan = function(ean) {
BrowserPrint.getLocalDevices(
function(printers) {
console.log("something happened");
},
undefined,
"printer"
);
};
My webpack config from the npm-package:
const webpack = require("webpack");
module.exports = {
entry: ["./src/index.js"],
output: {
path: __dirname + "/lib",
filename: "main.js",
library: "zpl-browser-print",
libraryTarget: "umd"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: require.resolve("./src/BrowserPrint.fat.js"),
use: "exports-loader?BrowserPrint=BrowserPrint"
}
]
}
};
When I import this module into my project and call printEan I get "Uncaught ReferenceError: finishedFunction is not defined". Which suggests that it finds the printEan function, and BrowserPrint, but somehow the code in the legacy script isn't being handled properly by webpack.
Looking at the code of the legacy script finishedFunction is never defined but then I have no idea why it works when I directly import the script in my other projects.
snippet from the legacy code:
c &&
((finishedFunction = function(e) {
if (((response = e), "" == response)) return void s(null);
response = JSON.parse(response);
var n = new t.Device(response);
s(n);
}),
i(void 0, c, finishedFunction, o),

Using jquery in a shared webpack module, which is outside the webpack root

I have multiple layouts, which depend on some shared typescript files, thats why I want to share this files with multiple layouts which are using webpack.
I'm trying to include jquery in my ajax.ts and get this error:
ERROR in ../_shared/ajax.ts
Module not found: Error: Can't resolve 'jquery' in '{...}/layouts/_shared'
_shared/ajax.ts:
import * as $ from 'jquery';
export class AjaxListener {
constructor(){
// use jquery with $
}
}
layoutA/app.ts:
import { AjaxListener } from "../_shared/ajax";
import { App } from "../_shared/app";
let app = new App();
let ajaxListener = new AjaxListener();
My Folder Structure looks like this:
/layouts
/_shared
/ajax.ts
/app.ts
/layoutA
/app.ts
/webpack.config.js
/package.json (contains "#types/jquery": "^2.0.47" and "jquery": "^3.2.1")
/tsconfig.json
tsconfig.json:
{
"compilerOptions": {
"module": "es6",
"target": "es6",
"sourceMap": true
},
"exclude": [
"node_modules",
"typings/browser",
"typings/browser.d.ts",
"typings/main",
"typings/main.d.ts"
]
}
webpack.config.js:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
var path = require("path");
var distPath = path.join(__dirname, "dist");
module.exports = [
{
entry: {
app: ['./app.sass', './app.ts']
},
resolve: {
extensions: [".tsx", ".js", ".ts", ".sass"]
},
cache: false,
output: {
path: distPath,
filename: "[name]_scripts.js"
},
module: {
rules : [
{
enforce: 'pre',
// "test" is commonly used to match the file extension
test: /\.js$/,
loader: "source-map-loader"
},
{
// "test" is commonly used to match the file extension
test: /\.tsx?$/,
exclude: [/node_modules/],
use: [ 'babel-loader', 'ts-loader' ]
},
{
test: /\.sass$/,
use: [
{
loader: "style-loader" // creates style nodes from JS strings
},{
loader: "css-loader", options: { sourceMap: true } // translates CSS into CommonJS
},{
loader: "sass-loader", options: { sourceMap: true } // compiles Sass to CSS
}
]
}
]
},
devtool: "eval"
}
]
If I try to import jquery inside layoutA/app.ts file (webpack root), it works fine. Since the ajax.ts lives outside this folder, which is the best way to import libraries like jquery, lodash etc. in these files?
The following points must be observed for the best way to load js libraries in your context:
Install every js library (e.g. jquery) with a package manager like npm
To each library it needs a TypeScript definitions file (e.g. #types/jquery, to find under npmjs.com)
Install this TypeScript definition file also with npm
Note every TypeScript definition in the tsconfig.json under "files" like
"files":[
"node_modules/#types/jquery/index.d.ts",
"node_modules/#types/requirejs/index.d.ts",
]
Do this compellingly (point 1-4) with the library requirejs. This is a js file and module loader.
Now you are ready to load the js library in the TypeScript main file like:
require(["jquery", "urijs"], function($, uri) {
// your code
});
Other notes:
In the index.html file: reference under the script tags only the js bundle files, builded by e.g. webpack.
Webpack needs a TypeScript loader.
Reference exported TypeScript classes in the tsconfig.json file under 'files' and also in the TypeScript file like:
import {Car} from "./classes/Car";
Hope it helps you, to get a proper structur!
Supplement:
Try the following: reference a js library in your ayax.ts like:
private example = require("./[path to npm library]/node_modules/test/src/test.js");
If you call the library name like 'test' in the require command, then its not possible to resolve 'test'. It try to resolve over the package.json and can not find it because its outside of the root.

New Class from IIFE function now working with webpack

So i have this function. I am trying to get a new Test('selector', {}) from outside this js file, it comes undefined and i can't seem to figure out why.
Do i really need to attach it to the window object ?
Can someone explain this ?
TO mention it works from the same file.
let Test = ((window, document, undefined) => {
class test {
constructor(selector, options) {
this.selector = document.querySelector(selector);
this.options = options;
}
}
return test;
})(window, document);
This is my webpack config file:
module.exports = {
entry: './src/test.js',
module: {
loaders: [
{
test: /\.js?$/,
exclude: / (node_modules) /,
loader: 'babel-loader',
query: {
presets: ['es2015', 'stage-0']
}
}
]
},
output: {
path: __dirname + '/src',
filename: 'test.min.js'
}
}
I was clearly misunderstanding what webpack is doing. Webpack turns all your JavaScript files into modules that are not available in the global namespace. That's why we need to use require/import to load them in. In the above example the Test function was never loaded in and is not defined. The default scoping nature of JavaScript no longer exists.

Categories