-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unification of plugin subsystems. #17
Comments
Good start. I'm actually less familiar with what you call the "standard format" plugins because the usage always seemed overly complex, so I never bothered with it. But In any case, I don't see anything in the docs pertaining to directory location of the old plugin format. The plugin file actually has to be "pre-loaded", does it not? So I don't think Less looks for it anywhere. Whereas Having If an author wants to follow a convention of using "less-plugin-" but wants to shorten the reference, they can just easily write:
Or whatever else they want to do there. Adding two specific words in the name of a JavaScript file doesn't make sense to make a dedicated feature, since names of files are arbitrary. AND, if they want plugins to do such prepending automatically, they can just add a "less-plugin-" prepending plugin! 😉
(Although the latter wouldn't be recommended, since it would be altering any plugin reference used anywhere. But still, it's an option.) I agree that adding "options" to plugins can be discussed later. |
It depends. Here you're thinking (as @rjgotten also most likely did when submitting the initial
We can get back to less/less.js#1861 (comment) and start all over again from scratch of course. But this is how the "standard format" works (
Then it is the issue of the docs to be fixed. Also speaking of the "old format". I guess we have to clarify this. The "standard format" is no way "the old" but actually the one and the only format. You may be less familiar with it but it's the only way by now to make something beside custom functions, so I recommend you to get more familiar with it and start looking where it has to be improved to meet your vision. Otherwise you're risking to continuously reinvent the wheel by proposing parallel incompatible solutions (like less/less.js#2852). In other words, it's not about making the |
That's fair. But what I meant was that we had talked about moving plugins to be loadable under
This is also a good point. I see where you're coming from. I had assumed that a) backward compatibility was not possible because of the way plugins are loaded, b) the solution might have to be to deprecate plugin format A, and have plugin format B ( But, you're not seeing them as "two formats", so much as a newer way (and maybe easier way) to call a Less plugin.
I hear you. Thank you for clarifying. One advantage we have is that I think we can classify the current |
One comment about loading order, though, as far as to which one is first. I still think the "explicit name" should be checked first. The original docs say, "We recommend the plugin starts "less-plugin" though that isn't required." "less-plugin" is supported, but it still seems like a convenience addition that it's checked after the explicit name is checked, as the name is an optional recommendation. So I think the name check would happen more like:
There's bridging |
Well, my proposal of the order is based solely on "the better performance for the masses" sense of view. Yet again assuming the majority of users will use "public/shared" modules (which will have the prefix for obvious reasons) instead of self-written plugins, it would make sense to not burden the majority with additional search step that rarely matches anyway. The only concern is the "browser-side" plugins: obviously since a node-based plugin has to be browserified somehow before to be used client-side, and that browserified version also ends in |
A public/shared module does not automatically mean an "npm-installed" one. The jQuery plugin ecosystem does not rely on Node as a dependency, and I don't think Less should either. Officially, Less is platform-agnostic. To me, that means that "core Less" should assume nothing other than the file reference you gave is a valid, local file. True, the platform extensions can then define what is "valid and local". So, it would make sense that the Node platform extension might have a different search mechanism / order than what happens in-browser. As an example that you're drawing from for convention: I think, conventionally, Less plugins have basically been "Node-only", partly because they rely on NPM, and partly because they're written in such a way that excludes the browser (relies on Node as a dependency). I think that's part of what has limited plugin adoption and ease-of-use, so I wouldn't want to see that entirely as a guiding principle. For instance, there are currently "Less plugins" that rely on NPM, explictly say they don't work in the browser, but are nothing more than additional Less functions. That's wholly unnecessary. I think for a successful plugin ecosystem, we should guide developers to create platform-agnostic Less plugins, since Less itself is platform-agnostic. Just like there were mistakes made with the initial Which certainly doesn't mean that we should break those plugins on a whim. I just want to really focus on:
I think those are the two most-essential pieces of a successful plugin system. If, in our documentation, we give examples that suggest, "It's okay to create a Node-only plugin", then we're effectively saying that Node is really the only valid Less environment, which makes non-Node/NPM plugin scenarios harder to use. So as to:
I think we should caution people against making "Node-side" or "browser-side" plugins as much as possible. Or at least thinking if such a thing is absolutely necessary. Rather than thinking of Less as a Node-based system that you add to with NPM-hosted plugins, it might be easier for some users to treat Less plugins like jQuery ones, where the plugins travel with the associated Less project, not that get installed somewhere "in-system", as those have dependencies which are not portable. Less plugins can easily exist as a single JS file which gets copied into your local Less source file directory. But, the nice thing is that if we do things right, we don't have to adopt either "philosophy". Someone could choose to manage their Less plugins via NPM, and another person could copy and paste a file. If we write guidelines and examples appropriately, people can have the best of both worlds. |
Just as an example as far as cross-platform plugin conventions, I've seen a library like Ractive (which can run in both browser and Node), basically do conventions as:
One thing that PostCSS does is it has a plugin generation script which creates your initial repo. So, we could do something similar where someone enters the name of their plugin, and it does the following:
|
This is probably better to be done with something like |
Yeoman may be what was/is used, in fact. So, yep, something along those lines. |
Actually; I wrote It was meant as a zero-setup, collision-free means to include custom functions for Less frameworks. The zero-setup aspect makes it work transparantly with any side-by-side editor that calls out to Lessc to create CSS previews, without having to mess with custom settings. That's an additional perk and may be a good reason to also develop your own project-specific functions in the
Imports may use URLs, so why not |
same mistake as:
Notice there's no mention of @plugin "clean-css"; is fine to load PHP-written (optionally even particular platform-specific, e.g. Wordpress)
This is illusion (no offense meant :) - it stops to be true as soon as you start to share the same plugin in different projects (so you either need a copy of the plugin in each or setup the shared paths, thus -> no zero-setup) or start to use different libraries that include different versions of the same plugin (so sooner or later someone comes with an idea of 'Less plugin manager/packager' utility). So in general it's all the same problems whatever approach you choose. |
Different versions of the same plugin are only an issue if you use NPM to manage your plugins. NPM is famously terrible and being able to install multiple versions to use simultaneously. The way @rjgotten wrote it seems like it would work fine in that scenario, since functions are scoped, and the included JS file doesn't contain any sort of unique identifier that can collide if you have multiple copies. Therefore, you should be able to do: #library1 {
@plugin "library1/foo-plugin.js";
bar: foo();
}
#library2 {
@plugin "library2/foo-plugin.js"; // same plugin, different version
bar: foo();
} Standard/current format plugins modify Less at the core/root level, and so are inherently prone to collisions in the way you describe. Currently And then you have everything that "builds" your final CSS file all in one place. It's not pulling your Less file from a local directory and then a JS file from some other place, even though they have similar URL paths. The way I visualize this is like: |
I also agree though that this is probably the wrong way to go:
Unless...... like the way that "less-plugin-" is magically prepended for lessc, we assign a meaning to the browser. (The browser knows to look in node_modules.) That said, it's such a specific case that, again, the "node://" URL scheme should itself be supported by a plugin. With proper architecture and good documentation, we can keep the core light and support those kinds of customizations better. I've said it before in other threads: there's no reason there can't be official plugins that pull in optional JS files. We have official platform extensions, so something that searches |
Ah. You beat me to it. Yeah; that was going to be my argument as well. Also, just for the record: Node.js module lookup is different from what Lessc is currently using. Lessc is using So it should be called something different from |
Well, it's not hard to guess what is the key differences between our visions. Some treat plugins as a method to extend a particular project, others think of it as an extension to the language/compiler to meet that project needs (hence first are biased towards local copy-pasted js files, second favor shared libs). And there's no way to prove anything but by practice itself.
Yep (for me there's no way to think of |
For me, there's no way to think of those things but part of a local project, since the local Ironically, the existing format is like a PHP environment, where available libraries are not guaranteed unless they are installed at the system-level, which leads to the reality where PHP !== PHP. Node.js solved this by allowing packages to be installed to a local folder by default. Such that running that project is, by and large, reliable and consistent. But, with current Less plugins, if "Awesome Less Library" relies on plugins, and you take your
While you may have a preference for managing modules using a specific package manager, Let's say you have this: @import "lib/awesomeless";
awesome();
// lib/awesomeless.less
@plugin "awesome-functions";
// ... stuff that relies on awesome-functions Say Awesome Less Library is importing that plugin via NPM, and at some point releases a new version: The user updates the NPM plugin, but fails to copy the new The question of how you manage that is rhetorical, because I'm sure smart brains here could come up with a clever method. The point is: with Now, ALL THAT SAID, the best scenario still may be to support either method. I'm obviously of the mind of which one should be recommended. I would prefer that the current plugin format migrated towards the Those two philosophies are not necessarily mutually exclusive. My hope is that there is a middle ground for all. |
This fixes one problem (aka "dll hell"), but introduces others. For instance it implicitly enforces "never update" approach simply because even with zillion tools to track/automate this, still even a minor update of a project with a lot dependencies becomes a disaster (see
Yep, and that's just what #17 (comment) says. Technically our dispute is just about what |
Some of that I didn't quite follow in the second paragraph because of some typos maybe? I couldn't quite decipher "cannot not scoped" and "plugin AS uses". But I think the gist is that you're talking about the benefits of a package manager. There are probably pros and cons of each approach. So, what it comes down to for me isn't "should the user be using a package manager" but "should we force the user to use a package manager". To me, the latter is an obvious no. It would be better if we didn't force platform nor packaging, as Less.js itself does not. Is it okay if a plugin is only Node-specific? Sure. But if Node is not an absolute requirement, there's no reason for a plugin to not be agnostic, and I think demonstrating that to users would help bolster the platform. So, let's step away from the philosophy of it because it's not getting anywhere. Let's accept that some people will want to use X plugin via My suspicion is that some good examples of using So, basically, my thinking is that:
@seven-phases-max @rjgotten How does that sit with you? |
@seven-phases-max Specifically on point #1 above, if |
Yes.
Yes; you don't want to lose the utility of having hassle-free, collision-free plugin functions being included via frameworks that users may incorporate into their own projects.
That's reasonable.
If it can be automated succesfully, then that sounds like a good idea.
If you want to combine local file references and global module references, then you have to be very careful with scoping and always ensure that, in the event of a name collision, there's always a way for the more specific local scope to overrule the global scope. This is particularly important with Node.js modules, as the lookup can crawl a huge number of paths to find a potential matching name, including globally installed Node.js modules installed for all user accounts, over which the current user running the Lessc compiler may have zero control. So here's my proposal:
If you have to load a plugin file that sits next to the less file that imports the plugin (and there is no path separator), then a user can explicitly request the local file by using the 'current directory' token, like so: |
Given a URL to the module itself, employ the following process:
The real trick is replicating the module lookup tho'... |
If you're running the Less compiler in Node, can't you you just do For the browser, grabbing a
Another great solution. I like the idea of specifying local-only with |
(@rjgotten - As a random aside, why aren't you on this list? https://github.com/orgs/less/people) |
The point I was trying to make was not related to performance or complexity, but the fact that a module name could be matched with a lot of places that are potentially not under a user's control. So you should make sure users always have a way to bypass the global lookup. (This is especially important for framework authors that want to distribute plugin files with their less files, as they have zero control over the target environment, period.) Hence my suggestion to use However, to answer your question: yes, the module lookup probably could just use E.g. for plugins installed side-by-side with the compiler itself, you could just assign the
Honestly; I can't guarantee available time to commit to the Less project. Too much going on. So, I'd rather stay off the official lists. |
There's no point to use |
So I have another idea (a bit breaking but just to consider). Maybe then we just should stop calling This of course won't mean that certain |
We originally separated the keyword from import to prevent confusion with less or css imports tho. |
Yes and no. First mentions of And for reasons we both wrote above, |
Ho boy. Hmm.... In a way, you're suggesting what I've been suggesting earlier, which was to have Plugin format A, and plugin format B, and have both exist in parallel. (Although the goal as I saw it was to move from A to B over time.) I get your reasoning. In the case of something like In contrast, with many The danger is, of course:
That said.... we don't necessarily avoid user confusion by having two plugin "formats" that have different loading mechanisms, if we indeed still call them both "plugins". That may, in fact, be more confusing. True, we were hoping to make the formats the same, but they would still have two different loading mechanisms / rules. The benefits might be:
It's a radically different direction than where we've been going, so I'm glad you brought it up. I think.... that could work as long as we would clearly delineate use cases, as in:
Now, both of those would have some possible overlap. And I still think the two could move a bit closer together (moving devs away from "node-only" plugins, allow adding visitors,etc. from "scripts") The one thing I would disagree with is overloading I think if we do that and separate the two, we also toss out all the complicated Node searching. It's just not worth the additional cognitive burden. We could still easily pass "scripts" to the initial Less options / setup that someone wanted available globally. This is not a bad idea. It does seem a hell of a lot easier to implement, it's a bit more pragmatic, and may actually cause less confusion. @rjgotten What are your thoughts? |
Another thing about |
Some other options to put on the table here: @component "foo";
@module "foo";
@require "foo"; // may semantically support the scoping behavior
@extension "foo";
@addon "foo";
@expansion "foo"; Would be nice to come towards an agreement on the name so we could start to document it. |
@less/core Bump. I think we have a plan we just need to agree on a name. |
I think my preference right now might be |
A dev friend of mine just suggested |
I'm a fan of A couple others to throw out there:
|
@matthew-dean suggested |
Second on |
Yep, I prefer it to something like 3 points for Griffyndor. I mean, |
@rjgotten Is that a +1 then? |
@matthew-dean |
Soooooo........ @rjgotten @seven-phases-max Here's a little progress update. TL;DR: I figured out that the two formats actually needed to be unified after all, and went ahead and did the work. Detailed ExplanationI started work on updating However.... I began to run into some problems. The right vars / objects weren't exposed to the inline scripts, so those pieces from the PluginManager needed to be exposed. Then, I realized there were some use cases that couldn't really be handled by a script running once and then exiting. The Meanwhile, I came across the plugin loader used by lessc. It handles retrieving a plugin by module name, and does a lot of convenient things like checking the version #, trying to prepend "less-plugin-" if no match is found, and if a plugin returns a function instead of an object, it attempts to create a new object out of it, which the Less.js API itself doesn't do, but should. All of that led me to the inevitable conclusion that @seven-phases-max's instincts were right all along, that really, the historical "function script" Once I had the idea that the two formats might need to be unified after all, I obsessively programmed to see if this was a possibility, and eventually I had a branch with these changes.
Based on initial testing, all of this works. I was able to do this: @plugin "inline-urls"; // loads less-plugin-inline-urls module installed via 'npm install';
@plugin "local-file"; // loads local-file.js located in the same folder as the file where this was invoked Note that the behavior of Node module loading is handled by the less-node plugin manager. I still need to test the less-browser plugin loader, which would only support local files. (Anything else would be too expensive in the browser, and it doesn't make sense to support Node in browser mode.) Final Thoughts: I still have some more work to do to:
I added these changes to the Feedback welcome. |
👍 Btw., indeed re-reading posts above I can see now that probably we digged too deep into "where plugin is loaded from" thing, which is in general quite subtle issue and won't be worth that possible overengineered |
@seven-phases-max Yes, I realized that too. It's actually quite a minor point. On Node the file searching order is not that relevant and really not that expensive. And I think I got stuck on having similar behavior on two different platforms, which is also not necessary. The compiling environments are very different, and it's easy to say, "On Node, Less will search node_modules first. In the browser, it won't." My plan is to have the browser loader do normal Once I got into the codebase and started digging/coding around plugins, the way you had described it made more sense. The tricky part was to fold in existing Meaning: as I currently have it written, a plugin doesn't have to export anything. It can add one function when it's first evaluated and be done (since the "functions" registry is now passed into the scope, as long as I have to do some work yet to fix If you have a chance to look and make any suggestions, please do. |
Sounds good. 👍 |
I'm recommending that we do a 3.0 release with these plugin changes sooner rather than later. I've disabled inline JavaScript along with the plugin changes because of a security report that was sent to me. Talk to me offline if you want more details. |
Documentation well on its way! https://github.com/less/less-docs/blob/3.x/content/features/plugins.md |
Update on I realized in the process of documenting plugin usage (which I now realize is a good practice for figuring out if intended usage actually makes sense), that there were problems with a few specifics in the "format" of the file. Namely: having globals like
So, what I've decided to do (but let me know if there's any opposition) is to recommend in the documentation specific boilerplate code that's essentially UMD format. (See the recently updated: https://github.com/less/less-docs/blob/3.x/content/features/plugins.md) I can (and probably will) still leave Once again, if anyone objects, let me know, but the boilerplate code should allow plugins to be loaded in Node.js, either via |
What would help me is if someone could get repos set up with cross-platform UMD plugins, to replace:
I'm not sure of a use case for a cross-platform file-manager. A plugin that connected to the Dropbox API? Seems silly in the browser. But the point is just to get a few plugin examples that are universal, so the use case doesn't matter that much. If someone can also come up with a repo layout like: - src/ // can be directly "require'd()"
- index.js
- otherStuff.js
- UMD-wrapper.js // wraps module in boilerplate for browser build
myplugin.js // builds for browser
myplugin.min.js
gulpfile.js // makes the above browser builds
package.json // has necessary includes for building a plugin That keeps plugins simple & flat, and can keep source files organized while outputting single-file builds. |
@matthew-dean It's kind of sad that the browser-globals based version bloats the UMD config because of a need to detect prior existence of the global var ns = root;
ns = ns.less = ns.less || {};
ns = ns.plugins = ns.plugins || {};
ns.push(factory()); Or maybe just forego the two-tier namespace altogether and just use a global (root.less_plugins = root.less_plugins || []).push(factory()); Also, my gut tells me the In total: (function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
(root.less_plugins = root.less_plugins || []).push(factory());
}
}(this, function () {
// Less.js Plugin object
return {
install: function(less, functions, pluginManager) {
// functions.add('')
}
};
})); |
Ideally, yes, but that would break existing plugins. The function registry is something I added, otherwise there's no "local" function registry passed to install, as it was previously just a global var. Renaming to Yes, you are right that I was trying to make it so Less config could appear before or after plugin So, yeah, I could make it another global to shorten it, but I would capitalize Update: actually, you're right on another count. It does need to be a second global, because otherwise you'll have people do this: <script src="first-plugin.js"></script>
<script src="second-plugin.js"></script>
<script>
less = {
plugin: [secondplugin]
};
<script src="less.min.js"></script> |
Also, to be more accurate, |
Docs updated: http://lesscss.org/3.x/features/#plugin-atrules-feature |
Should this discussion be closed for cleanup? |
Closing as implemented in v3 (for various feature spin-offs mentioned during the discussion please create dedicated tickets). |
As was already mentioned initially (and then in #10 and in various topics here and there),
@plugin
feature needs further improvements as its current implementation was just a temporary solution to get something in the release ASAP.So the very first step for the unification would be to allow the
@plugin
directive to load both formats, i.e.:less-plugin-clean-css
(in the directories specified in the docs)clean-css.js
file (of the "easy-functions" format, in the directories of the compiled less project).Contrary:
looks directly for an "easy-functions"
clean-css.js
file.As also mentioned earlier, loading "standard format" plugins with
@plugin
directive is expected to have certain acceptable limitations/restrictions to work properly (e.g. no special scoping and the directive should appear before any code it may have an effect on (specifically for "visitor" plugins)).Setting "standard format" options via
@plugin
are yet to be specified later. Obviously the options will be set via typical@import/@media
-like syntax, e.g.:But exact implementation details to be set up later.
The text was updated successfully, but these errors were encountered: