Webpack Fabric external is resolving to undefined - javascript

I'm setting up a project (typescript, webpack) with a couple of js libraries, configured as externals in webpack. They should not be part of the bundle, instead provided by script tags within the html.
But when trying to use them in my class, they resolve to undefined.
Fabric configured as an external in webpack is resolving to undefined
An error occurs when trying to set up the fabric js library as an external within a (typescript + webpack ) project. Fabric should not be bundled in the output file since it will be the responsibility of the consumer to provide (eg. through a browser script tag).
Note: jQuery initially had an issue (as an external) but is now resolved, and works as expected. Fabric on the other hand does not.
fabric has been configured as an external so that it will not be included in the webpack bundle.
Here's how...
Added as an external within the webpack.config.js
...
externals: {
jquery: 'jQuery',
fabric: 'fabric',
},
...
Installed the declaration files for both libraries
npm install #types/jquery -D
npm install #types/fabric -D
Added the libraries in public folder and index.html (since they must not be part of the app bundle)
<script src="js/lib/jquery.min.js"></script>
<script src="js/lib/fabric.min.js"></script>
Created a class App.ts, imported and implemented instances of these two libraries. (see App.ts)
import { fabric } from "fabric";
import $ from 'jquery';
fabric resolves to undefined within the class App.ts with the error:
TypeError: Cannot read property 'Canvas' of undefined
Please don't recommend ProvidePlugin or installing Babel.
More about webpack "externals": https://webpack.js.org/configuration/externals/
Update #1
jQuery is now working as an external library. I was not referencing the actual jquery global "jQuery" in the externals setup. I had "JQuery" (with a capital J). That's now resolved and jquery is working. Thanks #Aluan
Fabric on the other hand seems to be a different issue altogether.

What you're looking for is called shimming. Webpack docs cover this extensively here: https://webpack.js.org/guides/shimming/
Edit to add example:
In your webpack.config.js plugins array:
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
EDIT:
I pulled down your code and got it working. Here are the steps:
ts-loader chokes on shims, so use babel's #babel/preset-typescript -- otherwise you'll need to find a way to tell the ts compiler to ignore them. This will get you started:
npm install --save-dev #babel/core #babel/cli #babel/preset-env #babel/preset-typescript core-js#3
In your root, create a file called .babelrc and add the following:
{
"presets": [
"#babel/preset-typescript",
[
"#babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3"
}
]
]
}
Add this to your webpack.config.js:
plugins: [
new ProvidePlugin({
$: "jquery",
jQuery: "jquery",
fabric: "fabric"
})
]
Also update ts-loader, changing it to babel-loader.
Now in your code, you'll need to prefix your shimmed libraries with window:
constructor(private readonly selector: string, canvasHeight: number, canvasWidth: number) {
window.$(`#${selector}`).replaceWith(`<canvas id="${selector}" height=${canvasHeight} width=${canvasWidth}> </canvas>`);
this.canvas = new window.fabric.Canvas(`${selector}`, { selection: false });
}

It turns out that the issue with fabric is from fabric itself! The reason fabric is resolving to undefined (when being configured as an external on webpack) is related to the way that fabric exposes its library for consumption. It's an issue they need to fix.
I've added an issue on the official fabric github page
But there is a quick solution for us. Just import using CommonJS like this:
const fabric = require('fabric');
Now it works!

Related

Why does my Webpack bundle include jQuery twice?

I set up a parent module with 2 submodule dependencies. The parent module has no specified jQuery dependency, but each submodule specifies jQuery ^3.3.1 as a dependency (results in 3.4.1 for each submodule). I Webpacked the parent module and then I see in the generated bundle file that jQuery 3.4.1 is included twice. What should I be doing so that the same version of jQuery isn't included twice? I did try the splitchunks plugin and it did generate chunks but jQuery was still in there twice. I thought that Webpack is supposed to automatically analyze dependencies in the module graph and optimize the bundled code? I haven't yet tested NPM peer dependencies or the Webpack de-dupe plugin. I'm also wondering if there's something about jQuery itself to where Webpack can't/decides not to de-dupe automatically?
In both submodules' index.js files, I'm using:
import $ from "jquery"
In each submodule's package.json I specified:
"dependencies": {
"jquery" : "^3.3.1"
}
Then I did a npm install on each submodule.
module.exports = {
resolve: {
alias: {
// fix every jQuery to our direct jQuery dependency. Shariff 1.24.1 brings its own jQuery and it would be included twice without this alias.
'jquery': __dirname + '/node_modules/jquery/',
},
},
};
Note -happens because Shariff 1.24.1 defines jQuery as its own dependency instead of defining it as peer dependency in the package.json.
Refrence
I found that module resolution can automatically happen downwards (into child folders) of an entry point folder, but not upwards (in module folders outside of the entry point folder). I found that I needed to "tell" Webpack about these other module locations by adding a "resolve" object to Webpack.config:
resolve: {
modules: [
path.resolve(__dirname, "src/modules/pagetype/node_modules"),
path.resolve(__dirname, "src/modules/sitewide/node_modules"),
path.resolve(__dirname, "src/modules/template/node_modules"),
"node_modules"
]
}, // resolve

People ask for the # character in import form '#' in nodejs or vuejs [duplicate]

For example:
import Component from '#/components/component'
In the code I'm looking at it behaves like ../ going up one level in the directory relative to the file path, but I'd like to know more generally what it does. Unfortunately I can't find any documentation online due to the symbol searching problem.
The meaning and structure of the module identifier depends on the module loader or module bundler. The module loader is not part of the ECMAScript spec. From a JavaScript language perspective, the module identifier is completely opaque. So it really depends on which module loader/bundler you are using.
You most likely have something like babel-plugin-root-import in your webpack/babel config.
Basically it means from the root of the project.. it avoids having to write things like import Component from '../../../../components/component'
Edit: One reason it exists is because import Component from 'components/component' doesn't do that but instead search in the node_modules folder
Know it's old, but I wasn't exactly sure how it's defined, so looked it up, came by, dug a little deeper and finally found this in my Vue-CLI (Vue.js) generated Webpack config
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'#': path.join(__dirname, '..', dir)
}
},
so it's an alias which in this case points to the root of vue-cli generated src directory of the project
Update: As correctly mentioned by #aderchox in the comments, this is a general Webpack feature and not limited to Vue
To make Ben's answer more comprehensive:
First you need to add babel-plugin-root-import in your devDependencies in package.json (If using yarn: yarn add babel-plugin-root-import --dev).
Then in your .babelrc add the following lines into plugins key:
"plugins": [
[
"babel-plugin-root-import",
{
"rootPathPrefix": "#"
}
]
]
Now, you can use #. For example:
Instead of
import xx from '../../utils/somefile'
You Can
import xx from '#/utils/somefile'
As said above, this feature is not in JS by default. You have to use a babel plugin to enjoy it. And its job is simple. It allows you to specify a default root source for your JS files and helps you map your file imports to it.
To get started install through either npm:
npm install babel-plugin-root-import --save-dev
or
yarn add babel-plugin-root-import --dev
Create a .babelrc in the root of your app and configure these settings to your taste:
{
"plugins": [
["babel-plugin-root-import", {
"rootPathSuffix": "the-preferred/root/of-all-your/js/files",
"rootPathPrefix": "#"
}]
]
}
With the config above, you can simply import from that source like:
import Myfile from "#/Myfile"
without doing all this funky stuff:
"/../../../Myfile"
Note that you can also change the symbol to anything like "~" if that floats your boat.
I am using VS code to build react native Apps.
What you need is:
create a jsconfig.json under root path of your App
in your jsconfig.json, add the following code:
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES6",
"module": "commonjs",
"paths": {
"#/*": ["src/*"],
"#components/*": ["src/components/*"],
"#core/*": ["src/core/*"]
}
},
"exclude": ["node_modules"]
}
basically like "shortcut" : ["abs_path"]
In case you are using Typescript, you could achieve this by simply using your tsconfig.json like this:
{
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"#lib/*": ["app/lib/*"]
}
},
}
// # is an alias to /src
Inspired by Can Rau's answer I made a similar discovery in my src/views/Home.vue file. This file was created with the latest (July 2021, Ubuntu 20.04) versions: npx #vue/cli create myfirstvue --default.
I "inferred" it was /src but wanted to know why, because Ben's accepted answer said it would be the root of my project, which in fact is the parent, of /src.
Here is Home.vue:
...
<script>
// # is an alias to /src
import HelloWorld from '#/components/HelloWorld.vue'
</script>
It is defined by Vue Webpack template, which I learned from this other SO answer.
It is a way of remapping module paths, not part of the ES itself, you have to use babel import feature.

What does the # symbol do in javascript imports?

For example:
import Component from '#/components/component'
In the code I'm looking at it behaves like ../ going up one level in the directory relative to the file path, but I'd like to know more generally what it does. Unfortunately I can't find any documentation online due to the symbol searching problem.
The meaning and structure of the module identifier depends on the module loader or module bundler. The module loader is not part of the ECMAScript spec. From a JavaScript language perspective, the module identifier is completely opaque. So it really depends on which module loader/bundler you are using.
You most likely have something like babel-plugin-root-import in your webpack/babel config.
Basically it means from the root of the project.. it avoids having to write things like import Component from '../../../../components/component'
Edit: One reason it exists is because import Component from 'components/component' doesn't do that but instead search in the node_modules folder
Know it's old, but I wasn't exactly sure how it's defined, so looked it up, came by, dug a little deeper and finally found this in my Vue-CLI (Vue.js) generated Webpack config
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'#': path.join(__dirname, '..', dir)
}
},
so it's an alias which in this case points to the root of vue-cli generated src directory of the project
Update: As correctly mentioned by #aderchox in the comments, this is a general Webpack feature and not limited to Vue
To make Ben's answer more comprehensive:
First you need to add babel-plugin-root-import in your devDependencies in package.json (If using yarn: yarn add babel-plugin-root-import --dev).
Then in your .babelrc add the following lines into plugins key:
"plugins": [
[
"babel-plugin-root-import",
{
"rootPathPrefix": "#"
}
]
]
Now, you can use #. For example:
Instead of
import xx from '../../utils/somefile'
You Can
import xx from '#/utils/somefile'
As said above, this feature is not in JS by default. You have to use a babel plugin to enjoy it. And its job is simple. It allows you to specify a default root source for your JS files and helps you map your file imports to it.
To get started install through either npm:
npm install babel-plugin-root-import --save-dev
or
yarn add babel-plugin-root-import --dev
Create a .babelrc in the root of your app and configure these settings to your taste:
{
"plugins": [
["babel-plugin-root-import", {
"rootPathSuffix": "the-preferred/root/of-all-your/js/files",
"rootPathPrefix": "#"
}]
]
}
With the config above, you can simply import from that source like:
import Myfile from "#/Myfile"
without doing all this funky stuff:
"/../../../Myfile"
Note that you can also change the symbol to anything like "~" if that floats your boat.
I am using VS code to build react native Apps.
What you need is:
create a jsconfig.json under root path of your App
in your jsconfig.json, add the following code:
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES6",
"module": "commonjs",
"paths": {
"#/*": ["src/*"],
"#components/*": ["src/components/*"],
"#core/*": ["src/core/*"]
}
},
"exclude": ["node_modules"]
}
basically like "shortcut" : ["abs_path"]
In case you are using Typescript, you could achieve this by simply using your tsconfig.json like this:
{
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"#lib/*": ["app/lib/*"]
}
},
}
// # is an alias to /src
Inspired by Can Rau's answer I made a similar discovery in my src/views/Home.vue file. This file was created with the latest (July 2021, Ubuntu 20.04) versions: npx #vue/cli create myfirstvue --default.
I "inferred" it was /src but wanted to know why, because Ben's accepted answer said it would be the root of my project, which in fact is the parent, of /src.
Here is Home.vue:
...
<script>
// # is an alias to /src
import HelloWorld from '#/components/HelloWorld.vue'
</script>
It is defined by Vue Webpack template, which I learned from this other SO answer.
It is a way of remapping module paths, not part of the ES itself, you have to use babel import feature.

How to use JQuery-UI with Aurelia

I started a new Aurelia app using the Aurelia CLI.
I installed JQuery and configured aurelia.json using the instructions at the Aurelia documentation:
http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/the-aurelia-cli/6
I then npm installed Jquery-ui.
I now need to know how to configure audelia.json to recognize jquery-ui.
In the Aurelia documentation this example is given on how to reference a module:
"dependencies": [
{
"name": "library-name",
"path": "../node_modules/library-name/dist/library-name"
}
]
The problem is that unlike when you download jquery-ui directly, the JQuery-ui module does not have an actual Jquery-ui.js file ( if it does I couldn't find it).
Thank you
The jquery-ui package doesn't include a "built" version of jquery-ui as far as I can tell. I finally got this working by using the jquery-ui-dist package, which includes the default jquery-ui.js and jquery-ui.css files.
npm install jquery-ui-dist --save
Now add it aurelia.json in dependencies for vendor-bundle:
"dependencies": [
"aurelia-binding",
...
"jquery",
{
"name": "jquery-ui-dist",
"path": "../node_modules/jquery-ui-dist",
"main": "jquery-ui",
"deps": ["jquery"],
"resources": [
"jquery-ui.css"
]
},
]
Notice we are loading jquery first. The "main" attribute tells it that it should load jquery-ui.js from that directory. The "deps" attribute tells it that it is dependent on jquery. Finally the "resources" attribute includes the default jquery-ui.css.
Now in app.html, be sure to require the css file:
<require from="jquery-ui-dist/jquery-ui.css"></require>
To use in a ts file:
import * as $ from 'jquery';
import 'jquery-ui-dist';
I'm using Aurelia 1.0.X, after updating I needed these two imports for using any jQuery-UI widget, in this case draggable. It also works when importing slider or resizable.
import $ from 'jquery';
import {draggable} from 'jquery-ui';
In my package.json, my jspm dependencies are as follows:
"jquery": "npm:jquery#^3.2.1",
"jquery-ui": "github:components/jqueryui#^1.12.1"
Add of copy of jquery-ui.js to your static folder and add this line to your constructor to the class you intend to use jquery-ui, Please note : it should reference to the location of your jquery-ui file
import { $ } from 'jquery';
export class Index
{
constructor(){
require('../../../../../static/assets/js/jquery-ui');
}
}

materialize-css Uncaught TypeError: Vel is not a function

I'm using webpack as my bundler/loader and I can load materialize css in fine (js/css), but when I try to use the toast, it says
Uncaught TypeError: Vel is not a function
I am including the library in the main index.js file by:
import 'materialize-css/bin/materialize.css'
import 'materialize-css/bin/materialize.js'
Does anyone know why this could be happening? Looking at the bundled source, the js for materialize is there.
Had a same problem & came up with somewhat simpler solution:
Only 2 things are needed to be done:
First: Import following in you root module like app.js
//given you have installed materialize-css with npm/yarn
import "materialize-css";
import 'materialize-css/js/toasts';
Second: if webpack, set following Plugin or get the velocity.min.js as global variable just like you would use jquery:
new webpack.ProvidePlugin({
"$": "jquery",
"jQuery': "jquery",
"Vel": "materialize-css/js/velocity.min.js"
}),
I'm also trying to use materialize-css with webpack and have also run into this issue (albeit not for the same reason). Materialize isn't really built with a module loader in mind, and use global variables. They also bundle dependencies into their script directly in a way you might not want in a webpack-workflow.
I have a setup not exactly the same as you but I'll share it anyways, hoping it will help, my webpack+materialize works like this in a file i've created;
/**
* custom-materialize.js
*/
// a scss file where we include the parts I use.
require('./custom-materialize.scss');
/**
* materialize script includes
* we don't use all the plugins so no need to
* include them in our package.
*/
require('materialize-css/js/initial');
require('materialize-css/js/jquery.easing.1.3');
require('materialize-css/js/animation');
// note: we take these from npm instead.
//require('materialize-css/js/velocity.min');
//require('materialize-css/js/hammer.min');
//require('materialize-css/js/jquery.hammer');
require('materialize-css/js/global');
//require('materialize-css/js/collapsible');
require('materialize-css/js/dropdown');
Then just install Velocity from npm npm install velocity-animate
and point the global Vel materialize use to that package instead in webpack.
new webpack.ProvidePlugin({
'$': 'jquery',
'jQuery': 'jquery',
'Vel': 'velocity-animate'
}),
you have to import css and js Files separately in your index.html
you must not import css file in index.js
Make sure that the uglifyJsPlugin is like this.
new webpack.optimize.UglifyJsPlugin({sourceMap: true, mangle: false})
mangle property should be false so that the variable names of your source file doesn't change when you minify.

Categories

Resources