1

I use npm and a gulpfile.js to essentially export npm packages to a 'lib' folder under 'wwwroot'; this works a treat and whenever I update a specific npm package if it's in my gulpfile.js watch list it'll push the contents to the 'lib' folder.

The issue I have is that I used to use a manually extracted copy of ocktokit-rest in order to query the public api for some repo data. Recently this has stopped working, I assume that GitHub has updated their api which has had some breaking changes for my old version of ocktokit-rest. So with that in mind I installed @Ocktokit/rest version 18.0.9 using the npm package.json. This then creates the following directory:

~/lib/@octokit/rest/

According to the docs I need to refence one of the index.js files inside this. So because Razor doesn't appreciate the use of the @ symbol in the path I use the following in my _layout.cshtml

<script src="@Url.Content("~/lib/@octokit/rest/dist-src/index.js")" type="module"></script>

I added the type="module" as I was initially getting some issues with the import statements inside of the index.js file.

Here's the index.js file contents at the above route:

import { Octokit as Core } from "@octokit/core";
import { requestLog } from "@octokit/plugin-request-log";
import { paginateRest } from "@octokit/plugin-paginate-rest";
import { restEndpointMethods } from "@octokit/plugin-rest-endpoint-methods";
import { VERSION } from "./version";
export const Octokit = Core.plugin(requestLog, restEndpointMethods, paginateRest).defaults({
    userAgent: `octokit-rest.js/${VERSION}`,
});

This then raises the following error in the chrome debugger:

Uncaught TypeError: Failed to resolve module specifier "@octokit/core". Relative references must start with either "/", "./", or "../".

I don't particularly like the idea of adjusting the @octokit/ reference in favour of '../../' because then every time my gulpfile.js npm push task runs I'll have to manually change this file. However for the sake of debugging this I went through and adjusted index.js to look like this:

import { Octokit as Core } from "../../core";
import { requestLog } from "../../plugin-request-log";
import { paginateRest } from "../../plugin-paginate-rest";
import { restEndpointMethods } from "../../plugin-rest-endpoint-methods";
import { VERSION } from "./version";
export const Octokit = Core.plugin(requestLog, restEndpointMethods, paginateRest).defaults({
    userAgent: `octokit-rest.js/${VERSION}`,
});

When I did this I got similar error messages for each import that looked something like this:

index.js:4 GET https://localhost:44364/lib/@octokit/plugin-rest-endpoint-methods net::ERR_ABORTED 404

Now the above URL is pointed at the directory not a specific file, if I run the above through to a single file I can see it load in the browser and display the file. So If I type:

https://localhost:44364/lib/@octokit/plugin-rest-endpoint-methods/dist-src/endpoints-to-methods.js

I can see the js file displayed in the browser so I know it can be pathed to. Now Ideally I want to be able to use this package in another bit of custom js I wrote that iterates through my repos and creates nice little cards with all the info on, so I'm basically just trying to use it like this:

var octokit = new Octokit({ userAgent: 'agentName' });

But obviously the above is complaining about the existence of Octokit.

So I guess my question is, what the frack? I'm obviously missing something here so if anyone has any ideas in what direction I need to look or research I'd be very grateful.

It's probably nothing to do with the octokit package at all, and much more likely that I just don't understand how to properly import these types of JavaScript libraries into my asp .net core solution

JoeTomks
  • 3,243
  • 1
  • 18
  • 42

1 Answers1

1

There's a few parts of adding Octokit that you're having difficulties with: handling the @ symbol, the scope at which you import it, and the fact that you're trying to use files intended for build tools.

@ in a Razor Page

When you're writing JavaScript inline in a <script> tag inside the context of a Razor page, you'll need to escape the @ character by using @@. For example, if you were referencing the Octokit path, you would write @@octokit/rest.

Scope

When you're using type=module, your code has module scope, making you unable to reference the Octokit variable outside of the module. In order to break out of module scope, you can attach the Octokit variable to the window object:

window.Octokit = new Octokit({ userAgent: 'agentName' });

Then later on, your code in other script blocks can access Octokit like normal:

const { data } = await Octokit.request("/user");

Building Octokit

The files you're importing are not intended for direct consumption by the browser. It's expecting you to be importing it into JavaScript build tools, not importing it as a module directly from the browser.

The index.js file you're trying to import client side is intended to be used with some JavaScript build tools like Webpack. To get this working the way you want to in gulp, you would need to modify your gulpfile.js to include some kind of a plugin that would import @octocat/rest and output it into a file usable by a browser.

To do this with Webpack, you need to install a Webpack plugin for gulp: npm install --save-dev webpack-stream gulp-rename

Then, create a file next to your gulpfile.js called index.js that imports the library and does something with it:

import { Octokit } from "@octokit/rest"

window.Octokit = new Octokit({ userAgent: 'agentName' });

Now, modify your gulpfile.js to take index.js and pipe it through the webpack plugin:

const gulp = require('gulp');
const webpack = require('webpack-stream');
const rename = require('gulp-rename');

gulp.task('default', () =>
    gulp.src(['index.js'])
        .pipe(webpack())
        .pipe(rename("index.js"))
        .pipe(gulp.dest('lib/octokit/rest/'))
);

After running gulp, you should have an output file that has resolved all of the dependencies necessary for @octokit/rest!

Alternative Solutions

To save the trouble for this specific package, could you instead load Octokit from their CDN? The CDN handles all of the building and package resolution for you. Your code could then be:

<script type="module">
    import { Octokit } from "https://cdn.skypack.dev/@@octokit/rest";
    window.Octokit = new Octokit({ userAgent: 'agentName' });
</script>

(Note that @@ will escape the @ sign on Razor pages.)

Most packages offer a CDN option for loading their library client side without having to mess around with build tools. Even if they don't officially offer a CDN, sites like jsdelivr or unpkg can still offer a way to import these files.


Sometime in the future, it looks like browsers might support import-maps. Then, you would be able to handle the package resolution through the browser and do something like this on your Razor page:

<script type="importmap">
{
    "imports": {
        "/@@octokit": "/lib/@@octokit/"
    }
}
</script>
<script type="module">
    import { Octokit } from '@@octokit/rest';
    var octokit = new Octokit({ userAgent: 'agentName' });
</script>

It looks like this might be usable with a polyfill like system-js, where you would add the s.js loader and replace importmap with systemjs-importmap.

Kyle Pollard
  • 2,204
  • 1
  • 13
  • 27
  • The web-pack suggestion I didn't know about so that's definitely helped me understand the gulp pipeline in terms of creating the required js for this and other libraries and I've tried it out for Octokit and it's done exactly what it's supposed to so thank you for that. However I'm still getting undefined errors in my custom .js files that reference Octokit for example. I do actually have the same issue with one other npm library that I've included in the same way. Is it possible I am hitting some JavaScript dependency issues or scope issues? – JoeTomks Nov 13 '20 at 10:18
  • Ah, I hadn't realized you were referencing Octokit in several other scripts. This would be because you're importing it in a script with `type=module`, which has module scope and can't be referenced outside of the module. I've edited my answer to attach the Octokit object to the window so it has global scope. You should be able to reference the Octokit object in any other script now! – Kyle Pollard Nov 13 '20 at 16:56
  • see I'm not really a JavaScript expert in anyway shape or form and I didn't understand that there was different types of scope depending on the way the scripts were imported which now I think about it obviously makes a lot of sense. One final question, do you happen to know why vs moans about the @ pointer in import statements in modules and suggests changing them to ../ for example? – JoeTomks Nov 13 '20 at 22:30
  • 1
    I'm not sure. If you're in the context of a Razor page, you might want to make sure you're escaping the `@` by using `@@`. It might also be complaining because you can't directly import JavaScript modules from the browser by starting with the `@` symbol (like we do for `import { Octokit } from "@octokit/rest"` in the script above), it wants you to import directly from a path like `/lib/@octokit/rest` – Kyle Pollard Nov 13 '20 at 23:28