I have a webpage that I'd someday like to host. I've installed a few packages in the dev folder using npm, and they appear in the node_modules sub-folder.
I'd like to use these packages in a js module, but the import statement doesn't work as the docs lead me to expect.
Taking gsap as an example:
npm install gsap
Given that, and html that looks like this:
index.html
<html>
<head>
</head>
<body>
<div id="app"></div>
</body>
<script type="module" src="./index.js"></script>
</html>
I expect I should be able to have a js module that looks like this:
index.js
// per https://greensock.com/docs/v3/Installation
import { gsap } from "gsap"; // ERROR HERE
console.log(gsap);
Uncaught TypeError: Failed to resolve module specifier "gsap".
Relative references must start with either "/", "./", or "../".
Placing a/, or a ./ or any other combinations of paths I've tried produces a not found error. The only thing I can get to work is this, which I figured out by digging around in the node_modules folder...
import { gsap } from "/node_modules/gsap/gsap-core.js"; // this works, but yikes
console.log(gsap);
I must be doing something wrong to have to know that path and file name. I have a few packages I'd like to use, and I hope to not have to investigate each one to find where the actual module file resides.
Can somebody set me straight?
import { gsap } from "/node_modules/gsap/gsap-core.js"; // this works, but yikes
There's nothing particularly yikes about that in principle.
Browsers are not Node.js. They do not work the same way as Node.js. They cannot go rummaging about your server's file system in order to look for a module based on just its name.
You have to give them a URL to the JS file you want to import.
What is yikes about this is that you are exposing your entire node_modules directory over HTTP, and you probably don't want to do that.
A typical approach to using modules installed via NPM in the browser is to use a bundler such as Webpack or Parcel which will take all the modules your code depends on and bundle them up in to a distribution for sending to the browser (while doing optimisations such as tree shaking).
Related
I'm loading vtk.js from unpkg in a simple index.html file like so:
<html>
<body>
<script src="https://unpkg.com/#babel/polyfill#7.0.0/dist/polyfill.js"></script>
<script type="importmap">
{
"imports": {
"vtk/": "https://unpkg.com/vtk.js/"
}
}
</script>
<script type="module">
import 'vtk/Sources/favicon';
import 'vtk/Sources/Rendering/Profiles/Geometry';
</script>
</body>
</html>
When I load this in the browser I get an error in the console:
Uncaught TypeError: Failed to resolve module specifier "vtk.js/Sources/Rendering/OpenGL/Profiles/Geometry". Relative references must start with either "/", "./", or "../".
vtk/Sources/favicon imports without issue, but vtk/Sources/Rendering/Profiles/Geometry fails.
When I look at the vtk.js source I can see the problem, Geometry isn't actually doing anything other than importing two other modules and it doesn't have an appropriate relative reference before it.
import 'vtk.js/Sources/Rendering/OpenGL/Profiles/Geometry';
import 'vtk.js/Sources/Rendering/WebGPU/Profiles/Geometry';
Since these files are not under my control I can't modify them to have the relative url prefix. Is there another option?
Note: I know I really should be actually bundling the package with my app, even vtk.js docs say so but they also seem to indicate that using unpkg is possible and may be useful for fast prototyping.
At this point I'll just install the package so I don't waste more time on it...but I'm still curious how I could get this working with the hosted vtk.js (or if it isn't possible)?
Thanks!
Is it possible to import, using only JavaScript ES6, modules from package like tippyjs (https://github.com/atomiks/tippyjs)?
I tried to locally install the package via npm and use the files like this:
import tippy from '../node_modules/tippy.js/dist/tippy.esm.js';
but the browser give me this error:
validation.ts:46 Uncaught ReferenceError: process is not defined
I have also tried, unsuccessfully, to "replicate" what the CDN is doing.
Thanks to what magic does the CDN work?
<script src="https://unpkg.com/#popperjs/core#2"></script>
<script src="https://unpkg.com/tippy.js#6"></script>
I know I am supposed to use CDNs, CDNs are great ecc., but I just want to have a single js module file in my HTML where all the import of my local files are:
<script type="module" src="js/modules.js"></script>
Can't rely only on CDNs.
What I am missing?
Please note that I'm not usign node.js, is just an html page and some JavaScript.
I don't think this can work with ES6 modules in the browser because of the way that tippy.js references popper. It uses the syntax import { createPopper, applyStyles } from '#popperjs/core'; which the browser can't understand. There's an issue about this on Github with regard to bootstrap, which has the same problem apparently.
I don't get the error you're getting by the way, I get the one in the issue. My HTML file is as below with tippy.js installed in the same folder:
<body>
<button id="myButton">My Button</button>
<script type="module">
import tippy from '../node_modules/tippy.js/dist/tippy.esm.js';
tippy('#myButton', {
content: 'Tooltip!',
});
</script>
</body>
Running this gives error:
Uncaught TypeError: Failed to resolve module specifier "#popperjs/core". Relative references must start with either "/", "./", or "../".
I don't think you can edit the import in tippy.js locally to fix this, either: popper's own imports then fail to work for me.
The workaround, as an answer to the issue says, is to use a module bundler like webpack if you want to create a .js file you can reference. This also begs the question of what the point is of an esm install if it can only be used from node.
Background
I'm trying to create a "buildless" JavaScript app, one where I don't need a watch task running to transpile JSX, re-bundle code, etc every time I save any source file.
It works fine with just first-party code, but I'm stuck when I try to import dependencies from npm.
Goal
I want to achieve this kind of workflow:
npm install foo (assume it's an ES module, not CommonJS)
Edit source/index.js and add import { bar } from 'foo'
npm run build. Something (webpack, rollup, a custom script, whatever) runs, and bundles foo and its dependencies into ./build/vendor.js (without anything from source/).
Edit index.html to add <script src="build/vendor.js" type="module"...
I can reload source/index.js in my browser, and bar will be available. I won't have to run npm run build until the next time I add/remove a dependency.
I've gotten webpack to split dependencies into a separate file, but to import from that file in a buildless context, I'd have to import { bar } from './build/vendor.js. At that point webpack will no longer bundle bar, since it's not a relative import.
I've also tried Snowpack, which is closer to what I want conceptually, but I still couldn't configure it to achieve the above workflow.
I could just write a simple script to copy files from node_modules to build/, but I'd like to use a bundled in order to get tree shaking, etc. It's hard to find something that supports this workflow, though.
I figured out how to do this, using Import Maps and Snowpack.
High-Level Explanation
I used Import Maps to translate bare module specifiers like import { v4 } from 'uuid' into a URL. They're currently just a drafted standard, but are supported in Chrome behind an experimental flag, and have a shim.
With that, you can use bare import statements in your code, so that a bundler understands them and can work correctly, do tree-shaking, etc. When the browser parses the import, though, it'll see it as import { v4 } from 'http://example.org/vendor/uuid.js', and download it like a normal ES module.
Once those are setup, you can use any bundler to install the packages, but it needs to be configured to build individual bundles, instead of combining all packages into one. Snowpack does a really good job at this, because it's designed for an unbundled development workflow. It uses esbuild under the hood, which is 10x faster than Webpack, because it avoids unnecessarily re-building packages that haven't changed. It still does tree-shaking, etc.
Implementation - Minimal Example
index.html
<!doctype html>
<!-- either use "defer" or load this polyfill after the scripts below-->
<script defer src="es-module-shims.js"></script>
<script type="importmap-shim">
{
"imports": {
"uuid": "https://example.org/build/uuid.js"
}
}
</script>
<script type="module-shim">
import { v4 } from "uuid";
console.log(v4);
</script>
snowpack.config.js
module.exports = {
packageOptions: {
source: 'remote',
},
};
packageOptions.source = remote tells Snowpack to handle dependencies itself, rather than expecting npm to do it.
Run npx snowpack add {module slug - e.g., 'uuid'} to register a dependency in the snowpack.deps.json file, and install it in the build folder.
package.json
"scripts": {
"build": "snowpack build"
}
Call this script whenever you add/remove/update dependencies. There's no need for a watch script.
Implementation - Full Example
Check out iandunn/no-build-tools-no-problems/f1bb3052. Here's direct links to the the relevant lines:
snowpack.config.js
snowpack.deps.json
package.json
core.php outputs the shim
plugin.php - outputs the import map
passphrase-generator.js - imports the modules. (They're commented out in this example, for reasons outside the scope of this answer, just uncomment them, run the bundle script, and they'll work).
If you are willing to use an online service, the Skypack CDN seems to work nicely for this. For instance I wanted to use the sample-player NPM module and I've chosen to use a bundle-less workflow for my project using only ES6 modules as I'm targeting embedded Chromium latest version so don't need to worry about legacy browser support, so all I needed to do was:
import SamplePlayer from "https://cdn.skypack.dev/sample-player#^0.5.5";
// init() once the page has finished loading.
window.onload = init;
function init() {
console.log('hello sampler', SamplePlayer)
}
and in my html:
<script src="./src/sampler/sampler.js" type="module"></script>
And of course you could just look inside the JS file the CDN generates at the above url and download the generated all-in-one js file it points to, in order to use it offline as well if needed.
Alright, I downloaded this off Github trying to run it locally/modify it. https://tympanus.net/Tutorials/InteractiveRepulsionEffect/interactive-repulsive-effect.zip
The main index.html calls the JS in with: <script type="text/javascript" src="app.0ffbaba978f8a0c5e2d0.js"></script> which seems to be a minified version of app.js which I want to modify.
File structure looks like:
I changed the html to: <script type="text/javascript" src="../src/scripts/app.js"></script> which is the correct filepath to the JS that makes the scene, but I then get
Uncaught SyntaxError: Unexpected string
on line 1 of app.js which is:
import 'styles/index.scss';
import Cone from './elements/cone';
import Box from './elements/box';
import Tourus from './elements/tourus';
I tried changing this path, it doesn't matter. It just doesn't "like" the line. What is going on here? How can I reference the editable JS file?
You can't. The JavaScript code you've got isn't ready to be run in a browser.
Those public/app.xxxxxxxxxx.js files are what's ready to run in a browser, and they're likely compiled by Webpack (or something similar). Your repository has some sort of "build" process in place - chances are you can look at the scripts section of package.json to see the available build commands.
Exactly, you have to place yourself in the first-demo folder and modify your app js. Then run
npm install
to install webpack and any missing packages (just once). Then you can run
npm run build
and it will rebuild your public folder with your changes. Better yet, you can just
npm run start
and you will see a hot reload of your changes when you modify app.js in
http://localhost:9000
I'm trying to use lit-html to save my self some time, but I'm having trouble getting everything set up correctly.
Electron 4.1.1
Node 11.15
As of 5 minutes before posting this, I've run npm install and electron-rebuild, no luck.
I use require() as one would with any other NPM package
var render = require('lit-html').render
var html = require('lit-html').html
console.log(require("lit-html"))
Unfortunately, I'm greeted with this error
In reference to the three lines of code above.
I don't see any problems with my code.
I've tried reinstalling lit-html through NPM to no avail. I would really love to use this library, but first I have to get over this hurdle. If I'm being honest, I don't know if this error is reproducible, but nothing I do seems to fix it. The problem seems to lie with node and the way that imports are handled.
Am I missing something here? Is this a common issue? If so, what can I do to fix it?
You need to transpile lit-html before you can require it
I tested require('lit-html') and I was greeted with this error:
/home/chbphone55/Workspace/test/node_modules/lit-html/lit-html.js:31
import { defaultTemplateProcessor } from './lib/default-template-processor.js';
It clearly states that the error is coming from lit-html/lit-html.js:31 where the line uses ES Module import syntax.
You can transpile it using tools like Babel or similar ones. However, you may want to try using ES Module syntax so you can import lit-html without transpiling it.
Example:
<!-- HTML File -->
<script type="module" src="index.js"></script>
// index.js
import { html } from 'lit-html';
What if you can't use type="module"
If you are unable to use the type="module" method above, you can also use the ESM package.
ESM is a brilliantly simple, babel-less, bundle-less ECMAScript module loader.
Here are a few examples of how to use it:
Using the node require flag (-r) to load esm before everything else
node -r esm index.js
Loading esm in your main file then loading the rest of your code.
// Set options as a parameter, environment variable, or rc file.
require = require('esm')(module/*, options*/)
module.exports = require('./main.js')