How does require('atom') work? - javascript

Atom exposes some global APIs that you can access from require('atom')
How does this functionally work? Atom packages don't explicitly have atom as a dependency, yet they can still do this. Moreover, how can I do this in my own Electron application with my own global package?

I've gone through and analyzed Atom's source myself to determine how this happens, and this is what I've come up with.
Atom packages are required using the normal node require. However, according to the apm readme:
The other major difference is that Atom packages are installed to
~/.atom/packages instead of a local node_modules folder...
So the require('atom') package isn't retrieved from a parent node_modules directory like normal node modules. Instead, Atom overrides the module loader to change the behavior a bit.
More specifically, they override Module._resolveFilename like so:
Module = require 'module'
Module._resolveFilename = (relativePath, parentModule) ->
resolvedPath = resolveModulePath(relativePath, parentModule)
resolvedPath ?= resolveFilePath(relativePath, parentModule)
resolvedPath ? originalResolveFilename(relativePath, parentModule)
It attempts to resolve the path of a module with its own module cache logic before defaulting to normal behavior. This is done for a couple reasons that I can tell.
It lets them hardcode the path of builtin modules like 'atom', even though the normal behavior would never have found it.
It prevents loading package dependencies twice when packages have the same dependency with compatible versions. If packageA loads lodash#4.x.x and later packageB attempts to load lodash#>=3, then Atom steps in and gives packageB the lodash that packageA loaded.

Related

Webpack can't resolve non-js `require`s from within node_modules

I've got a Next.js project configured to resolve imports that end in .web.js. This works outside of my node_modules directory. I did this by setting resolve.extensions = ['.web.js, '.js', '.jsx'] in my webpack config. I understand that this setting is responsible for resolving imports that don't have an extension, e.g. import _ from './component', when ./component.web.js exists.
I also have some node_modules that make use of this .web.js extension. They're private modules, but the idea stands. Let's say our node_modules looks like this. It may be worth noting that these modules have already been transpiled and as such use require rather than import.
- node_modules
- #foo
- bar.js
- baz.web.js
- baz.native.js
Now let's say that we have the following:
// bar.js
require("./baz");
If I try to import #foo/bar, the app will throw a module not found error on the line require("./baz"); saying that it can't be found. If I change it to require("./baz.web.js") or remove the line altogether then the app runs fine.
Why can webpack make these kind of resolutions outside of node_modules, but not within the directory? And how can I tell webpack to resolve those imports, too?
Depending on your module resolution strategy, you'll either find some files or not. Node.js resolves modules as outlined here. This means for you that require('./baz') is resolved to requesting /path/to/module/baz.js. Since your file is not actually named, it is not found. You can use require('./baz.web') instead.
As to whether Webpack can "automatically" handle which import to use, it probably comes down to using a plugin or having some sort of logic in bar.js to choose between baz.web and baz.native.

Ignore or replace dependencies' imports when bundling with Rollup

I'm bundling a JS library using Rollup. This lib has a dependency on #tensorflow/tfjs-core.
On tfjs's code, there's a function that fetches a URL. If it's in the browser environment, it uses the global fetch function; if it's not, it tries to import node-fetch.
Something among these lines:
fetch(path: string, requestInits?: RequestInit): Promise<Response> {
if (env().global.fetch != null) {
return env().global.fetch(path, requestInits);
}
if (systemFetch == null) {
systemFetch = require('node-fetch');
}
return systemFetch(path, requestInits);
}
My library is made to run in the browser, so it always uses the global fetch function. However, Rollup still bundles node-fetch's require in my lib's assets.
It should not be an issue, but some consumers are reporting errors when using the library in a React project that uses webpack:
Failed to compile.
./node_modules/[my lib]/index.js
Cannot find module: 'node-fetch'. Make sure this package is installed.
You can install this package by running: npm install node-fetch.
Question is: is there some way I can tell Rollup not no bundle this?
I thought about replacing the require('node-fetch') by undefined after the bundle is generated, but it feels like a dirty hack. Any other sugestions?
PS: I believe marking node-fetch as external on consumer projects would fix the issue, but since I do not use node-fetch in my lib, it would be nice to remove it from final output.
Other package managers can include or exclude files based on the environment, test, development, production, etc.
There is any number of ways of implementing this, even going so far as
# Makefile
ENVIRONMENT ?= test
ROLLUP = $(which rollup)
ENVSUBST = $(which envsubst)
rollup.config.js: src/$(ENVIRONMENT)
${ENVSUBST} < $# > $^
${ROLLUP} $^ -o $(ENVIRONMENT).js
If you created files named after your environments, you could compile them using
make -e environment=browser
I don't expect my code to work, only to express ideas.
There is this loc which is used to exclude node-fetch from the bundle. You could consider a similar approach in your rollup configuration. (I think) If you add that, node-fetch will/should not be a part of your minified library.

What version of an npm library is *actually* used?

I know that npm libraries, when installed, can install multiple versions of the same library in a hierarchical tree, like this:
a#0.1.0
-> b#1.0
-> c#2.0
-> b#2.0
In the above, package a at version 0.1.0 is pulled in, and its dependencies b#1.0 and c#2.0. Similarly, c#2.0's dependency is
pulled in, which is b#2.0.
I have heard from someone that, even though package b is installed at two different versions, only one of them is actually loaded into memory and used. I have also heard that this may or may not be the case for node deployments of javascript versus browser deployments.
So my question is: Is it true that only one package of b is loaded into memory? If it is, is this true for both node and browser, or are there differences?
Nodejs
Multiple versions of a Node module/library can be loaded, depending on where they are loaded from. The module loader is covered in depth in the Node.js documentation.
The require() call caches modules based on the resolved file path.
require('b') from module a will resolve to .../a/node_modules/b
require('b') from module c will resolve to .../a/node_modules/c/node_modules/b
So seperate modules will be loaded for the same call. This can be demonstrated with a small example.
Module B - node_modules/b/index.js
module.exports = {
VERSION = 'b-0.5.0'
}
Module C - node_modules/c/index.js
module.exports = {
VERSION: 'c-1.0.0',
BVERSION: require('b').VERSION,
}
Module C's copy of B - node_modules/c/node_modules/b/index.js
module.exports = {
VERSION: 'b-9.8.7',
}
Create a program to output the versions.
console.log('b', require('b').VERSION)
console.log('c', require('c').VERSION)
console.log('cb', require('c').BVERSION)
Then output the versions
→node index.js
b b-0.5.0
c c-1.0.0
cb b-9.8.7
So two modules with different paths using require('b').VERSION get a different value.
Traversal
It's worth noting that if a Node.js require('b') fails to find a locally installed ./node_modules/b then it will traverse up the directory tree to look for b in parent node_modules directories.
Using the above example case again, but removing a/node_modules/c/node_modules/b.
Module c does a require('b') but can't find ...a/node_modules/c/node_modules/b then it will go into the parent node_modules/ and look for b, In this case it would load the cached copy of ...a/node_modules/b
→node index.js
b b-0.5.0
c c-1.0.0
cb b-0.5.0
Browser
Browsers have no concept of an npm module. You need a loader that supports CommonJS module's like Browserify or Webpack. If the loader doesn't respect CommonJS/Node.js rules then it's unlikely to work with npm packages.
You should be able to package up the above example with your loader and see how it behaves.

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'
};

Using Browserify and RequireJS on the same page?

So i've come across an interesting use case where i'm using Browserify to bundle all of my assets together in a project, but a large external (external to the project) module needs to be loaded in when a certain in-app window is accessed. (It's a video player module made up of three scripts that get pulled in asynchronously when required).
At the moment i'm getting all kinds of errors from uncalled object errors if the requireJS module is loaded in before the Browserified app.js file, to cannot find module errors if loaded in after the Browserified code.
Is there anyway i can get Browserify and RequireJS to play nicely on the same page? I'm losing my mind!
TL;DR - use window.require in your browserified script.
Maybe it would still help someone.
I happen to use an 'external' dojo-based library totally based on requireJS-style AMD, which is absolutely un-"browserifyeble" and un-convertable to CommonJS or anything sane. My own code is totally Browserified. It's working OK like this:
Load the AMD loader (which defines the global require function) before the browserified script:
<script src="dojo/dojo.js"></script> <!-- RequireJS/AMD loader, defining a global 'require'-->
<script src="app/main.js"></script> <!-- The Browserify bundle -->
In your own js, call the require function on window object ('cause you'll have a local browserify-require shadowing the global one)
window.require(['dojo/dojo'], function (dojo) { ... });
The 'external' app or library, which calls require on its own to load submodules etc., works just fine 'cause that code is out of browserify context and the global require is not shadowed there.
Maybe if you have some pure nice standard RequireJS modules, you could somehow convert them to be Browserifiable, but in my case that wasn't an option.
There is a tool called browserify-derequire that resolves this issue by renaming browserify's require statmenets to avoid the naming collision.
It can be installed with npm using:
npm install -g browserify-derequire
Use it as a browserify plugin by changin your build command to:
browserify src/*.js -p browserify-derequire > module.js
There is more discussion on this issue at: https://github.com/substack/node-browserify/issues/790
For a gulp friendly solution (similar to what #Cride5 proposed) you can use gulp-derequire plugin.
Basic example from docs:
var derequire = require('gulp-derequire');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
gulp.task('build', function() {
var bundleStream = browserify({entries: './index.js', standalone: 'yourModule'}).bundle();
return bundleStream
.pipe(source('yourModule.js'))
.pipe(derequire())
.pipe(gulp.dest('./build'));
});
Plugin is also based on derequire module so all options are supported.

Categories