highlight.js: Vue.js plugin "ReferenceError: hljs is not defined"
A Vue.js plugin should be self-contained and not require an external script.
Currently however, the Vue.js component for highlight.js depends on hljs but hljs is not in scope in its definition.
https://github.com/highlightjs/highlight.js/blob/master/src/plugins/vue.js#L24
If a Vue.js user uses npm or yarn to install highlight.js, and follows the instructions in the section Using with Vue.js they will receive the following error in their browser.
[Vue warn]: Error in render: "ReferenceError: hljs is not defined"
The line that produces this exception is this one:
https://github.com/highlightjs/highlight.js/blob/master/src/highlight.js#L26
Proposal:
- Put the contents of the vue.js plugin file inside a function that accepts
hljsas an argument and returnsVuePlugin. Make the file export that function. - Change the plugins/vue.js import to import this new function instead of
VuePlugin - Change this location to invoke the wrapper function.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 2
- Comments: 39 (22 by maintainers)
Commits related to this issue
- fix(vue) Vue.js component should not require `hljs` global work (#2816) * closes #2815 — committed to highlightjs/highlight.js by esoterra 4 years ago
If you’re offering a plugin for Vue that provides a component, as a Vue developer I’d generally expect that component to be completely self-contained, where it imports the dependencies it needs. This makes it compatible with any module bundler which will automatically resolve and bundle those dependencies.
Depending on a global being in scope is uncharacteristic of what I think of as a modern Vue component, since Vue is used in many different contexts other than simply being included on a page. Vue is now used in projects (like the Gridsome static site generator, which does do Vue SSR) that manage dependencies automatically (users aren’t usually writing their own
<head>tags anymore, in many cases they’re being automatically managed by a build tool.)Updating the component to automatically import HLJS would allow it to be used anywhere where Vue components can be used, without any issues. If that option isn’t ideal (if it, for example, creates duplicate imports of HLJS) then dependency injection (like what’s implemented in #2816) is the next best option.
Well, I see arguments both ways. I’m trying to remember if the past issue or two were all Vue related or if there was some larger confusion. Globals are indeed dirty, but that ship has sailed on the core web build with
hljs… and I’ve had this thought before (about people using npm in the browser), so seemed worth considering. I’ll count both of you as “no globals” votes I think. 😃@joshgoebel there are import systems that don’t place all imports in global scope. If HLJS requires a variable
hljsto be defined in global scope, it’s incompatible with these import systems, and by extension certain environments.For example, in some environments you can do something like this:
The import of
hljshere will be pruned automatically by most import systems, since it isn’t being used anywhere. Modern import systems like this don’t rely on global scope at all.Done. https://github.com/highlightjs/vue-plugin/blob/main/README.md
I meant literally mentioning that Highlight.js uses a singleton instance. Maybe something like:
Added text emphasized.
I think something else here that I failed to put my finger on earlier is that many (most?) ES6 module imports import a class that’s instantiated using
newrather than a singleton. Maybe it’s just a “me” thing but I’m not used to the singleton pattern here.The docs explaining this probably clears up any confusion. I might explain the singleton pattern (and how ES6 modules actually implement and enforce that pattern) more explicitly rather than calling it “the magic of ES6 modules” but this otherwise looks great!
In this comment I wrote about the scope and pruning issues with the current approach.
The code sample you just provided looks like it solves both of these problems! 🙂
The pattern of importing
BuildVuePluginand then passing it anhljsinstance seems atypical. I understand what’s happening, but why can’t you just import a built Vue plugin directly? Also, since the Vue plugin will always require HLJS, why not declare it as a direct dependency instead of requiring it as an implicit peer dependency?Something like this would be what I’d expect this to look like:
Edit: I realize why you might not want to automatically import HLJS in the Vue component, since that wouldn’t allow the developer to selectively import different languages / control import bloat.
I think an appropriate solution for this would be to export
HLJSVuePlugin(or something like that) as the default export which would import and configure HLJS for all languages (the simplest developer experience) and then offer up theBuildVuePluginapproach from the same package if developers wanted more fine-grained control over the HLJS instance used in the Vue Plugin.That approach might look like this:
I’m just trying to understand the whole scope, because this has come up before… and if my PR had been merged 2 months ago (or you were using a browser build) then you never would have opened this issue because you’d never have run into the problem. So hence exploring whether one issues resolves the other or whether there are two different issues here. Measure twice, cut once and all. 😃
The only time to discuss many of these nuanced items is when they come up in a real world context… so it seems worth asking (even if we keep it in core) if perhaps it should be exported differently (whether the dependency should be inverted, etc), etc.
We need to do a better job of tracking the overall Highlight.js ecosystem - I have far too little what is out there other than 3rd party language modules… If there was already a great Vue plugin I’m not sure I’d have added it to the core library at all. But that ship sailed. 😃