Import external library to Angular 2 (Systemjs) - javascript

I'm trying to use this library in my Angular 2 project. I've tried:
npm install search-query-parse --save
And then:
Adding via <script> in index.ts - doesn't understand export in file (understandably)
Adding through RequireJS by adding it to the config file and then using import { searchQuery} from 'search-query-parser'; - I can see the file is loaded through the network inspector, though I can't use it... I get Unhandled Promise rejection: (SystemJS) Can't resolve all parameters
What am I missing?
EDIT:
Here is my system-config.ts (the relavant parts...)
map: {
'search-query-parser': 'npm:search-query-parser'
},
packages: {
'search-query-parser': { main: './index.js', defaultExtension: 'js'
}

It largely depends on the module itself in your case.
There are several ways to fix this, but the easiest one in this circumstance is using:
import * as searchQuery from 'search-query-parse'
This will fix the issue because it will import everything in the searchQuery constant, so you will use:
searchQuery.parse(params)
because the library exposes a .parse method.
Alternatively, you can also import the parse method only:
import { parse } from 'search-query-parse'
and use it as parse(params)
That is because the brackets notation ({}) will look exactly for the method / property exported by the module that exposes the name provided in the brackets.
If you still have no success with both methods, it's likely that the module is not being include at all by SystemJS (however, an error should be shown in that case).
There are several fixes for such an issue (where one of them is to search for a node or SystemJS module that actually is compliant with typescript), but the easiest one is looking for the HTML file where the systemjs config is included, locating the wrapper (System.import(app)) and, before such call, defining by yourself the module:
System.set("search-query-parse", System.newModule(require('search-query-parse'));
In this way, you're telling SystemJS that, at runtime, it needs to make the Node Module "search-query-parse" available under "search-query-parse", so using in typescript he vanilla-style require:
const sqp = require('search-query-parser');
Please note that there also are other ways to fix such an issue (usually through the systemjs file), however these likely are the easiest and portable ones.

This is typically how you'd bring in an external script such that it's bundled and available to the app (assuming you are using the CLI): in angular-cli.json, note the "script" element. Try putting it there, it will then be bundled and the deps will (hopefully) resolve. I've done this for a number of different scripts, they always work.
"mobile": false,
"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.css",
"../node_modules/font-awesome/css/font-awesome.css",
"styles.css"
],
"scripts": ["./app/my_script.js"],
"environments": {
"source": "environments/environment.ts",
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
Wrote an article on that: http://www.tcoz.com/newtcoz/#/errata

Related

How do I let the browser allow me to use imports? (TypeScript) [duplicate]

This question already has answers here:
How to resolve Node.js ES6 (ESM) Modules with the TypeScript Compiler (TSC). TSC doesn't emit the correct file-ext
(2 answers)
Closed 12 months ago.
Context
I have these two TypeScript files:
// something.ts
let x = 3;
export default x;
// index.ts
import x from "./something";
console.log(x);
under these tsconfig.json settings:
{
...
"target": "es2016",
...
// "module": "commonjs" /* <- commented out */
}
when I compile this into JavaScript, I get these two files (which is what I expect):
// something.js
let x = 3;
export default x;
// index.js
import x from "./something";
console.log(x);
Now, I've linked the index.js file from my HTML file like so:
<script src="./dist/index.js"></script>
Problem
When I try opening this up using Live Server, I get this error in the console:
Uncaught SyntaxError: Cannot use import statement outside a module
So, how do I fix this?
What I've tried
Adding back the "module": "commonjs" line in my tsconfig.json file
This results in a different error message appearing in the console:
Uncaught ReferenceError: exports is not defined
As per this SO post, a suggested solution is to comment out the "module": "commonjs" line, which essentially throws my situation into an infinite loop...
Adding "type": "module" to the <script> tag and package.json
From this SO post, a suggested solution is to add "type": "module" to the <script> tag:
<script type="module" src="./dist/index.js"></script>
but now I get this error message:
GET http://127.0.0.1:5501/dist/something net::ERR_ABORTED 404 (Not Found).
In the same post, another suggestion was to add "type": "module" to the package.json file:
{
"type": "module",
"dependencies": {
"typescript": "^4.6.2"
}
}
and from what I've observed, this doesn't really do anything.
You are really really really close. In ESM (in browser) imports, you must provide the file name extension, so you need to do:
import something from "./something.js";
instead of simply
import something from "./something";
Your other code didn't work because the browser runs on, well... the browser. That means we're not in a NodeJS environment so modules don't work so magically. So, the caveats of ESM imports:
You can't import npm packages
All imports must be an absolute URL or start with ./ or /
The browser doesn't automatically add a file extension; you'll have to specify yourself (e.g. import something from "./something.js"; instead of import something from "./something";)
You must specify type="module" in your HTML script tag to tell the browser that it's a module you're looking at
Please don't use this in production, because...
If you have nested imports, the browser can't load them concurrently; they have to be loaded one by one
It's not as fast (duh)
The feature is quite new so isn't really enhanced that much
The real solution:
Use a bundler instead!!
Why?
A bundler (the most popular is perhaps Webpack) finds all the imports and exports in your code and combines it all into one file.
Why you should use a bundler:
Everyone is using it
It's the standard
It's popular
...
It enhances page performance
You get other goodies like minification and whatnot
You can import (some) npm packages
That doesn't mean we should never use in-browser imports. It's totally fine when you're using it in development, as it's easier to set up anyways. Vite, a popular development framework, takes advantage of this to improve the DX (developer experience) by removing the bundler wait time every time you re-run your code.
But again, don't use this in production.
I suggest you use a bundler since you have Node and TS set up anyways. It'll be easier in the long run...

In Rollup How do I get --external to work on the command line directly pointing to the module to exclude? (no node resolve plugin)

The documents talk about using external in the context of the node resolve plugin, but I am not using that. I would like to exclude lit-html (which is native es6 modules) so that those imports remain in the bundle.
In my module I import them with import { html, render } from '../../node_modules/lit-html/lit-html.js'; and it works great in the browser.
I have tried every permutation of path including relative path like rollup --format=esm --file=dist/bundle.js -- src/main.js --external 'node_modules/lit-html/lit-html.js' and just get [!] Error: Could not resolve entry (--external).
It does not even say if the file is found, never mind what the problem is.
Seems your command is wrong, use -i to indicate the input file or try moving -- src/main.js to the end of the command without the dashes.
Regarding the external part, don't think it will work without using the exact id of the import but worth a try.
Using a config file:
module.exports = {
input: 'src/main.js',
external:[
'../../node_modules/lit-html/lit-html.js'
],
output: {
format: 'esm',
file: './dist/bundle.js',
sourcemap: true
}
}

Error: Can't resolve lodash-mixins in typescript angular2

I'm trying to add custom functionality to extend lodash (note lodash
is npm'ed in). But I keep getting a resolve error.
I've added a new file called lodash-mixins.js to my test project scripts folder e.g: project/frontend/src/web/Scripts/
var _ = require('lodash');
_.mixin({
mixinLoaded function () { console.log("lodash mixins are loaded");}
});
module.exports = _;
Overview Folder Structure (simplified)
project/frontend/src/web
...frontend.web.csproj
...angular-cli.json
project/frontend/src/web/Scripts/
...lodash-mixins.js
project/frontend/src/web/app/
...app.module.ts
I've manually added my "lodash-mixins.js" to the "angular-cli.json"
"apps": [
{
"scripts": [
"../node_modules/jquery/dist/jquery.min.js",
etc
"../node_modules/lodash/lodash.min.js",
"../Scripts/lodash-mixins.js", // <<<<< not picking up
"../Scripts/global-error-handler.js",
],
Test by changing existing reference in one of my test.service.ts
from:
"import * as _ from 'lodash';"
to:
"import * as _ from 'lodash-mixins';"
I've rebuilt my c# solution for good measure.
Run in CLI: $ng build --watch
ERROR in project/frontend/src/web/app/test/services/test.service.ts
Module not found: Error: Can't resolve 'lodash-mixins
Any ideas?
You're confusing two different things here.
1) The "scripts" config for Angular CLI tells WebPack to include those JavaScrip files in the output bundle. It doesn't add those as importable modules. They get loaded as if you added <script src="..."> tags to your HTML file (not exactly accurate, but gives the idea).
2) When you import using TypeScript it searches for the module. The reason it's giving the error is because the file isn't in one of the search paths. Had it actually found the file. It would have loaded it twice. Since you've already added it to the "scripts" config option.
JQuery, Lodash, etc.. etc.. can be loaded using modules or just plain global variables. When you add it to the "scripts" config, then I think this tells WebPack to load it in the global browser space.
When you use "import _ from 'lodash'" in TypeScript. You're actually resolving to the "#types/lodash" module which people often install so that TypeScript knows about the interface for lodash. When WebPack bundles everything for the browser it swaps out the #types for the real reference to the lodash instance.
So you have a couple of options.
1) Continue with what you've done. Add TypeScript definition file in the import path for your new module named "lodash-mixin". That module just defines the new interface with the new methods. The import will find that file and use it to compile the TypeScript, but the WebPack bundle will load your JS file in it's place.
2) Remove your "lodash-mixin" from the "scripts" config, then import using a relative path import _ from '../Scripts/lodash-mixins'. This is what I usually do. Note: You might have to add the file extension ".js"
3) Add your "Scripts" folder to your tsconfig.json as one of the type roots. This allows you to just use import _ from 'lodash-mixins'.
4) There is a way (and I forget exactly how), but you can tell TypeScript to alias lodash to your lodash-mixin so that all imports use that type instead. I don't recommend this approach.

How can I use babel-polyfill for multiple separated entries / outputs?

I have this in my webpack.config.js to create two different outputs from two sources:
module.exports = {
entry: {
'dist/index.js': ['babel-polyfill', './src/Component.jsx'],
'example/bundle.js': ['babel-polyfill', './src/Page.jsx'],
},
output: {
path: './',
filename: '[name]',
},
...
Compiling it with webpack works just fine, but if I load index.js in a browser I get this error:
Uncaught Error: only one instance of babel-polyfill is allowed
I need babel-polyfill for both outputs. What can I do?
When developing a library (as opposed to an application), the Babel team does not recommend including babel-polyfill in your library. We recommend either:
Assume the ES6 globals are present, thus you'd instruct your library users to load babel-polyfill in their own codebase.
Use babel-runtime by enabling babel-plugin-transform-runtime, which attempts to analyze your code to figure out what ES6 library functionality you are using, then rewrites the code to load the polyfilled logic from babel-runtime instead of from the global scope. One downside of this approach is that it has no way to polyfill new .prototype methods like Array.prototype.find since it can't know if foo.find is an array.
So my recommendation would be to remove babel-polyfill from your dist/index.js bundle entirely.
Use idempotent-babel-polyfill
import 'idempotent-babel-polyfill';
https://github.com/codejamninja/idempotent-babel-polyfill

Webpack importing video.js returns an empty object

I am trying to use video.js via webpack.
I installed video.js via npm - npm install video.js --save-dev
In webpack I read that video.js should be loaded via script loader else it throws an error.
This is how I am loading video.js through the babel loader
module:
loaders: [
{
test: /video\.js/,
loader: 'script'
}
]
I got this solution from here https://github.com/videojs/video.js/issues/2750
This is my import statement
import videojs from 'video.js';
The issue that I now face is the import is returning an empty object, so when I try to do this:
var vidTag = ReactDOM.findDOMNode(this.refs.html5Video);
this.videojs = videojs(vidTag);
I get this error:
renderer-0.js:8031 Uncaught (in promise) TypeError: (0 , _video2.default) is not a function(…)
Any help will be much appreciated. I am new to ES6 / React / Webpack
Please take a look at the loader's README before copy&pasting some random code. The script-loader is not appropiate here, because it imports scripts into the global scope while skipping the whole module system.
So, if you wanted to use the script-loader, you would just write:
import "script-loader!video.js";
console.log(videojs); // should be an object now
Usually I would not recommend the use of the script-loader because it neglects the whole point of a module system where you import stuff explicitly into the local scope. In the example above, the import happens as a side-effect into the global scope which is effectively the same as just using a <script> tag with all its downsides like name clashes, etc.
There are often better alternatives to it, like the exports-loader, which appends a module.exports at the end of the module, thus turning an old-school global script into a CommonJS module.
In this particular case, however, you don't need a loader at all because video.js is already aware of a CommonJS module system. Just write import videojs from "video.js";.
There is another minor problem, however. If you compile this with webpack, it will print a warning to the console:
WARNING in ../~/video.js/dist/video.js
Critical dependencies:
13:480-487 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
# ../~/video.js/dist/video.js 13:480-487
This is because webpack detects that this file has already been bundled somehow. Often it's better to include the actual src with all its tiny modules instead of one large dist because this way webpack is able to optimize the bundle in a better way. I've written down an exhaustive explanation about how to import legacy scripts with webpack.
Unfortunately, video.js does not include its src in the version deployed at npm, so you're forced to use the dist. In order to get rid of the error message and to improve webpack's build time, you can instruct webpack to skip video.js when parsing the code for require() statements by setting the module.noParse option in your webpack.config.js:
module: {
noParse: [
/node_modules[\\/]video\.js/
]
}
Usually it's safe to flag all pre-bundled modules (typically those with a dist folder) as noParse because they are already self-contained.
include SDN
<script src="//vjs.zencdn.net/5.11/video.min.js"></script>
webpack config:
config.externals = {
'video.js': 'videojs'
};

Categories