Obsidian.md Plugins: ''obsidian' not defined' - javascript

I am currently writing a plugin for obsidian.md that renders math functions inside a code block. I used webpack for bundling node libraries like yaml and function-plot. In the config, I added 'obsidian' as an external. The plugin builds with a warning about the bundle size but that doesn't matter since it's local anyways. When I add the plugin, it always says ''obsidian' not defined'. I guess it's looking for obsidian in the global context and can't find it? Here's the repo: https://github.com/leonhma/obsidian-functionplot
Do you know how to configure webpack properly? There's probably some really easy fix but I'm also new to typescript, webpack and developing plugins for obsidian..

Thank you #håkon-hægland for your suggestion (why didn't I think of that?). First of all, the file generated by webpack looked like
function(obsidian) {
... (around 300kb code)
}(obsidian);
, so webpack tried to access some global object called 'obsidian'. The important part in webpack.config.js was
...
externals: [
obsidian: 'obsidian'
],
...
As per your suggestion, i took a look at the other repo, and they use
...
externals: [
obsidian: 'commonjs2 obsidian'
],
...
That fixed my problem and now obsidian is properly imported at runtime. Just posting this in case someone else has this problem, as i couldn't find an existing answer myself.
PS: For those interested, since you are most certainly developing obsidian plugins: It was also really important to set output.libraryTarget to commonjs or commonjs2 inside the webpack config.

Related

Using Babel/Webpack only for transpilling/polyfilling

I have a very old javascript code base and I do not want to use the modern way of compiling all of the javascript files into one using standard webpack because it is not possible due to the way the website code is written.
But I want to write new scripts using modern Javascript (e.g. Promises and Fetch) but still be able to support old browsers like IE11.
I have configured webpack and babel so it gets multiple entry javascript files and for each of them it does the classic transpiling/polyfilling using #babel/preset-env and corejs.
This works and polyfills every script based on the babel target config but it creates one issue. It encapsulates global variables/functions in the script so they are not accessible from other scripts which reference them (yes old javascript). Is there a way to disable this structural modifications?
Also I know I could use only Babel without Webpack for this but the problem is when I try to polyfill e.g. Fetch I have to use https://github.com/github/fetch which cannot be just used with Babel afaik.
Any help appreciated.
I think inevitably your refactorings are modernizing the code, and if you are not careful, one day you can end up bundling everything with webpack;)
The set up you describe, I achieved with with:
module.exports = {
entry: {
messages: "./src/messages",
"hello-world": "./src/hello-world",
},
output: {
library: {
type: "global",
},
filename: "[name].js",
},
};
every export from each file is put directly on window - if you load files in the right order, you can have invisible dependencies maintained across the codebase.
To this setup, you can add babel loader with presets as you stated in your question. Besides, you can tart doing the explicit imports across different files - even if function X is available on global scope, you can migrate some places to import/require it explicitly.
If you want to play with my code yourself, you can find it here:
https://github.com/marcin-wosinek/webpack-legacy/
and here is the example in action:
https://marcin-wosinek.github.io/webpack-legacy/

VS Code shows module not found even though WebPack build works

My VS Code says that it can't find an import even though my WebPack build still works.
Here is the import...
import * as tf from '#tensorflow/tfjs';
and the message from VS Code:
Cannot find module '#tensorflow/tfjs'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?
I have read something about path aliases which can be set up in the tsconfig.json to shorten long paths to modules. But if this is a path alias and I don't have it configured in my tsconfig.json, how does WebPack know where the module is located?
I also read that the convention for path aliases is to start with an "#" but the folder in the "node_modules" itself is called "#tensorflow", so i don't know if it really is a path alias and if not, maybe WebPack magically knows that it has to search in "node_modules" for this module?
As you can see i'm really confused about this and i would be greatfull if somebody could clear this up for me and explain what i must do to stop VS Code from complaining about the import.
Found the solution on my own.
I only found stuff about defining the aliases in the tsconfig.json expclicitly in the "path" option, but this couldn't be the answer to my problem because in my other Angular projects there is nothing like this defined even though I'm using #Angular imports there a lot without this problem.
But then I found this in my Angular project "moduleResolution": "node".
As stated in othe typescript documentation:
However, resolution for a non-relative module name is performed differently. Node will look for your modules in special folders named node_modules.
And behold, it works. Yes I could have probably tried this earlier, since its written in the message from VS Code from my question, but i though this was something for only node.js specific projects and I didn't read about this anywhere.

Environment variables in svelte + rollup

I'm looking for a straightforward way to set up environments. I.E. It would be great if I could run npm run dev:local and npm run dev:staging which load different environment files which are accessible at runtime via process.env. In understand it's compiled so I may have to access the variables in a different way. I'm using svelte with rollup straight from sveltejs/template. It should be simple but I see no way of doing it. It's cumbersome, but possible to do with webpack. Is there a simple way to do this?
You can inject build time constants in compiled code with #rollup/plugin-replace.
Something like this:
rollup.config.js
import replace from '#rollup/plugin-replace'
...
const production = !process.env.ROLLUP_WATCH
export default {
...
plugins: [
replace({
'process.env': production ? '"production"' : '"dev"',
}),
...
]
}
Note the double quotes of the value: '"production"'. The plugin injects the string as is in the code so, if you want a string, you need quotes in the quotes.
Also, as mentioned in the plugin's docs, it should be put at the beginning of your plugins array to enable optimizations like shaking out dead code by other plugins that follows it.

WebPack sourcemaps confusing (duplicated files)

I decided to try out WebPack on a new project I'm spinning up today and I'm getting really strange behavior from the sourcemaps. I can't find anything about it in the documentation, nor can I find anyone else having this issue when skimming StackOverflow.
I'm currently looking at the HelloWorld app produced by Vue-CLI's WebPack template -- no changes have been made to the code, the build environment, or anything.
I installed everything and ran it like so:
vue init webpack test && cd test && npm install && npm run dev
Looking at my sourcemaps, I see the following:
This is a hot mess. Why are there three version of HelloWorld.vue and App.vue? Worse yet, each version has a slightly different version of the code and none of them match the original source. The HellowWorld.vue sitting in the root directory does match the original source, but what's it doing down there instead of in the ./src/components folder? Finally, why isn't there a fourth App.vue that has the original source for it?
As far as I can tell this may have something to do with the WebPack loaders. I've never gotten these kinds of issues with any other bundler, though. Below is an example of the exact same steps using the Browserify Vue-CLI template:
No webpack:// schema, only one copy of every file, the files actually contain the original source code (kind of important for source maps), no unexpected (webpack)/buildin or (webpack)-hot-middleware, no . subdirectory,.... just the source code.
I haven't worked with Vue so can't really describe how exactly this is happening but it seems to be related to Vue Loader. Looking at the documentation I did not really find anything that clarifies why it would create three different files for one component. But it does seem logical considering that a .vue file might contain three types of top-level language blocks: <template>, <script>, and <style>.
Also, looking at two of those files you do see a comment at end of each file that suggests it was modified in some way by a Vue loader. Either this
//////////////////
// WEBPACK FOOTER
// ./node_modules/vue-loader/lib/template-compiler
or
//////////////////
// WEBPACK FOOTER
// ./node_modules/vue-style-loader!./node_modules/css-loader
The third file is different but it still does have code that identifies it as being modified by Vue loader. Here is some of that code
function injectStyle (ssrContext) {
if (disposed) return
require("!!vue-style-loader...")
}
/* script */
import __vue_script__ from "!!babel-loader!../../node_modules/vue-loader/..."
/* template */
import __vue_template__ from "!!../../node_modules/vue-loader/..."
/* styles */
var __vue_styles__ = injectStyle
The document also says this:
vue-loader is a loader for Webpack that can transform Vue components written in the following format into a plain JavaScript module:
Which explains why you might not see the same type of behaviour with other bundlers.
Now, This might not be the answer you were looking for but just wanted to share what I had found.
This is actually a feature of webpack.
webpack has HMR (Hot Module Reloading). If you look in your network tab, go ahead and make an update to your HelloWorld.vue file. You'll see a js chunk come thru as well as an updated JSON manifest. Both of these will have a unique hash at the end for each time you make a change to the application. It does this so the browser does not have to do a full reload.
For a better explanation of this I would highly recommend reading through https://webpack.js.org/concepts/hot-module-replacement/

RequireJS: bundling module with plugins

I'm using an external library that consists of a "core" plus multiple "extensions". Every extension depends on the core. Think jQuery or Rx.
What I need to do is to bundle the core together with some of the extensions and provide that as a single module. On the surface, it seems that something like this should work:
// lib.js
define(
"lib",
["./Lib/lib", "./Lib/ext1", "./Lib/ext2"],
function(lib) { return lib; }
);
The problem, however, is that extensions expect the "core" to be available by the module ID of "lib". In other words, "ext1" is defined like this:
// Lib/ext1.js
define( ["lib"], function(lib) { lib.ext.someFunc = ... } );
One can spot the problem here: because the name "lib" refers to my "bundled" module instead of just the "core", it is not yet available at the time ext1 loads, so the whole chain becomes circular and falls apart.
Of course, I could map the core to "lib" and then give my bundled module a different name:
// main.js
require.config( { paths: { lib: "Lib/lib" } } );
// lib.js
define(
"bundled-lib",
["./Lib/lib", "./Lib/ext1", "./Lib/ext2"],
function(lib) { return lib; }
);
But that approach is highly undesirable for a few reasons:
It's just plain inconvenient to use a different name. There is no good common sense name that I could use instead, "lib" is pretty much the only option, and anything else will look ugly.
But more importantly, this may lead to hard-to-catch bugs. Down the road, when I have forgotten all about this little hack, I may just follow my common sense and import "lib" instead of "bundled-lib", and then my extensions will not get loaded. Or sometimes they will. If some other module which correctly imports "bundled-lib" just happens to load before the new "lib"-importing module, then it will work. Otherwise, it won't. Meaning that my application will either crash or not depending on whether certain features have or have not been used.
So the bottom line is, I would like to bundle the core with extensions, call the bundle "lib", but somehow have the extensions to only import the core, while not modifying the extensions themselves.
Any ideas anyone?
This sounds very similar to jQuery and jQuery plugins. Seems like you have an understanding of the implications, so you just need to make a decision on which method is preferred.
I would not go with a module that returns 'lib', which already has those extensions. If you feel feel that you only need single dependency where you reference extended lib, just go with your 'bundled-lib' approach.
To stick with best practices and not confuse yourself in a future I believe it would be best not to bundle, but for those modules where you rely on extensions, include dependency to your core lib and those extensions:
define(['lib', 'ext1'], function(lib){
// module definition...
});
This way it is VERY clear what are dependencies for this module. I'm sure you thought about it and I just hope it help you with making a decision.
So just a few hours after asking the question, I have found the answer on my own.
The answer is - the map config of RequireJS.
Basically, as it turns out, I can define a mapping of module names as seen by each module individually.
In particular, my problem as it is described in the question, would be solved by the following configuration:
require.config( {
map: {
'ext1': { 'lib': 'Lib/lib' }
'ext2': { 'lib': 'Lib/lib' }
}
} );
This will make both ext1 and ext2 see module 'Lib/lib' as 'lib', while not affecting all other modules.

Categories