npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@plotdb/rescope

v5.0.14

Published

rescope your code with any JS libraries

Downloads

87

Readme

@plotdb/rescope

experimental project. Load and scope any external JavaScript and reload scope on demand.

For example, assume here are the list of js url we'd like to load, which kept in libs variable:

  • assets/lib/bootstrap.native/main/bootstrap-native.min.js
  • assets/lib/bootstrap.ldui/main/bootstrap.ldui.min.js
  • assets/lib/@loadingio/ldquery/main/ldq.min.js
  • assets/lib/ldcover/main/ldcv.min.js
  • assets/lib/ldview/main/ldview.min.js

We can load all above js files with rescope, with a resolveed context containing all imported variables:

scope = new rescope!
scope.init!
  .then -> scope.load libs
  .then (context) -> myfunc(context)

Once loaded, we can access context for those libraries:

myfunc = ->
  scope.context libs, (context) ->
    {ldcover, ld$} = context
    # now ldcover and ld$ are available ...
    ldcv = new ldcover do
      root: ld$.find('.ldcv', 0)

This is useful when you need the same library with different versions:

d3 = do
  v3: 'https://d3js.org/d3.v3.min.js'
  v6: 'https://d3js.org/d3.v6.min.js'

scope = new rescope!
scope.load d3.v6
  .then -> scope.load d3.v3
  .then -> scope.context d3.v6, ({d3}) -> /* run v6 code with local d3 variable ... */
  .then -> scope.context d3.v3, ({d3}) -> /* run v3 code with local d3 variable ... */

While it's possible to load context into window object, we may run into trouble with concurrent overlapped context calls, so the context is always available as local variables. Always rely on the passed context object to access required libraries.

Semantic URL / Module Loading

Instead of plain URLs, you can also request a library with its name, version and relative path. For example,

{url: "assets/lib/@loadingio/ldquery/main/ldq.min.js"}

can be represented as:

{name: "@loadingio/ldquery", version: "main", path: "ldq.min.js"}

This abstracts the location of libraries and thus can be customized if needed with registry option:

new rescope({registry: function(opt) {  return opt.name + opt.version + opt.path; });

Return promise from a registry function call for a directly content resolving - in this case you should return an object in following form:

  • version: exact version of the return object.
  • content: content for the requested resource.

registry can also be an object with url and optionally fetch as a member function. Check @plotdb/block and @plotdb/registry for advanced registry usage.

where registry, if provided, should be a function:

  • accepting an object with following members:
    • name: module name
    • version: module version
    • path: relative path to the requested file
  • and return corresponding url based on those members, for example:
    • return ["https://jsdelivr.net/npm", opt.name, opt.version, opt.path].join('/');

Customized Context

Libraries can be loaded correctly in one single load invocation because rescope take care of the scope issue in load stage for us.

However,

  • if we need load in separated stages, we may run into trouble of missing dependencies to libraries loaded earlier.
  • Additionally, we may want to load libraries into a specific context stored before.

To keep track of the context loaded , we pass an optional object directly into load:

scope.load libs, (ctx = {}) .then -> ...

once loaded, the empty object ctx above will be filled with the loaded objects from libraries in libs. This ctx can then be used in turn in the following load calls:

ctx = {}
scope.load libs1, ctx
  .then -> scope.load libs2, ctx
  .then -> scope.load libs3, ctx
  .then -> ...

in this manner, following libraries libs2 and libs3 can be loaded with the context of previous loaded libraries libs1 without them being written in 1 load call.

Asynchronous Script Loading

By default all script are loaded asynchronously. You can force them loaded in synchronous manner, by extending URL into object with following options:

  • url: URL to load
  • async: load asynchronously if set to true. default true.

Delegate Window

By default rescope uses iframe window to preload libraries and peek variables they defined. The iframe is called delegate window. Apparently behavior for the host and the delegate is not the same.

We specify an option delegate and set it to false to tell @plotdb/rescope that this instance doesn't use delegate ( itself is a delegate ):

new rescope({delegate: false});

Additionally, you can also run code within delegate's context, by setting 'useDelegateLib' to true:

new resope({useDelegateLib: true})

This will only work when delegate is set to true ( which is by default ). With useDelegateLib set to true, all libraries loaded with the rescope object will work under a separated window and document object. Please note, it won't work as expected when cross refer libraries between two different global scope, so don't mix up libraries in different global scope.

Even with useDelegateLib set to true, you can still enter host context by setting the second parameter to false when calling context:

res = new rescope({useDelegateLib: true});
res.context("some-lib", false, function() { ... });

Caching

Instead of downloading libraries everytime, you can also precache libraries into a single js file.

After downloading all necessary libraries, get js for caching by:

ret = rescope.cacheDump();

the return value ret is the runnable JS string which insert cached libraries into rescope class. Or, manually inject cache by calling:

rescope.cache(
  "some-url",
  {
    code: "code",
    vars: [list of string for available variable names in dependency]
  }
)

Polyfills

use prejs when constructing for inserting pre-required JS into both host and delegate environment:

new rescope({prejs: ["https://...", ...]});

TODO

  • Browser compatibility check
    • works in all major browsers ( latest Chrome, Firefox, Safari, Opera, Edge )
    • doesn't work in IE11
  • Performance benchmark

Note

  • This is not meant to be used for sandboxing or for security reason. @plotdb/rescope never prevent any scripts from accessing document, and all scripts are still run in the main thread.
  • some libraries such as d3 may check and use object with the name they are going to use if exists. Thus we always have to restore context in case of disrupt their initialization process.
  • rescope mimics window object but there are still limitations. If a library declares a variable by window.somevar but accessing it with somevar, it will fail.

Limitation

rescope uses proxy object to replace global objects such as global, self, window and this, so accessing global in library will actually be accessing the proxy object.

However, there are still ways to get the actual window object, such as from event.source of message event:

global.fire("message", "hello");
global.on("message", function(event) { event.source });

Some libraries check event.source before using it like:

if(event.source == global) { ... }

Unfortunately this will fail, since event.source (the real global) is not the same with global (the proxy object). Since proxy object should never be equivalent to the proxied object, this can't be solved in @plotdb/rescope side - libraries will have to be patched if we want to use them in @plotdb/rescope.

Resources

  • ShadowRealm may help in what we want to do:
    • https://github.com/tc39/proposal-shadowrealm/
  • Realm, predecessor of ShadowRealm
    • Note: Realms proposal has been superceded by the ShadowRealm Proposal
    • https://github.com/tc39/proposal-realms/#ecmascript-spec-proposal-for-realms-api
    • https://github.com/Agoric/realms-shim
      • based on their current statue: OBSOLETE, INSECURE, NOT RECOMMENDED FOR USE
    • https://www.figma.com/blog/how-we-built-the-figma-plugin-system/
  • also check how Vue does its own scoping in template:
    • https://github.com/vuejs/vue/blob/v2.6.10/src/core/instance/proxy.js#L9

License

MIT