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
Advertisement
Answer
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
.