Open chuckdumont opened 10 years ago
It is worth noting that the LESS maintainers want to support custom path resolution in version 2.0
: https://github.com/less/less.js/issues/2107
It sounds like we need the custom path resolution support in order to properly handle nested imports (from https://github.com/less/less.js/issues/2107 - "it appears that the paths option for the LESS parser doesn't do anything on the client.") , but I'd be reluctant to try and use it for supporting cross-package AMD modules directly through the imports statement. Trying to do so would introduce ambiguity in how imports are resolved when an import path matches both an AMD path or package definition, and a relative path location. We could support a special syntax for import file names (e.g. @import url('AMD:packageA/mixin.less'), but this could cause problems with syntax highlighters/checkers, and would make the file unusable without the custom path resolver. I think that until native support for AMD is added to less compilers, using AMD to load less resource is best left to an AMD loader plugin.
There's currently a proposal in the LESS issue tracker for supporting npm imports (https://github.com/less/less.js/pull/1972).
Currently, LESS has the following syntax for importing CSS but treating it as LESS:
@import (less) 'path/to/file.css';
The proposed syntax for npm modules is as such:
@import (npm) 'module/file.css';
We could support something like this:
@import (amd) 'package/file.css';
I like that approach. A couple of comments.
The @import modifier (amd) is likely not enough information by itself in order for the compiler to be able to load the resource. A loader plugin name (e.g. dojo/text) to use would likely also need to be provided, although this could be done through config arguments.
Rather than have the LESS compiler implement support for an AMD loader, I'd prefer to see generic support for named import loaders implemented in the compiler so that third parties could register named loaders to use for with the @import modifier. So when the compiler encounters an import like
@import (foo) 'path/name.less'
then it invokes the foo loader to load the resource. The loader would provide the contents of the resource, as well as the context path to use for nested imports that don't specify a modifier name. Of course, the compiler would need to support async completion of the resource load.
Are you volunteering to provide the implementation for this support in the LESS compiler :)?
A common practice when working with LESS files is to import other LESS files that don't declare any CSS, but instead define variables, etc. that are used by the importing file. This works fine if the imported files are co-located relative to the importing file, but does not work if you want to import files that are located in another AMD package because the LESS compiler does not use AMD to import modules.
With CSS files, you can use the CSS loader plugin to require CSS files from different AMD packages simply by naming them in the dependency array of a require() or define() call. This doesn't work for LESS modules, though, because of the need for the LESS compiler to compile the modules as a unit in order for variables declared in one module to be visible to another module.
One solution, of course, is to pre-compile your LESS resources into CSS and use the CSS loader plugin to require the compiled files. However, it would be desirable, particularly for development, to avoid this pre-compile process, and still support the ability to have LESS resource in one AMD package depend on LESS resources in another AMD package.
The LESS loader plugin
Here, we propose the use of a LESS loader plugin with support for special syntax and config properties that allow multiple LESS modules to be required as a single compilation unit. The special syntax allows multiple modules to be specified, separated by the '|' character. For example:
When not using the aggregator, the LESS loader plugin will use the text loader plugin to load the specified modules and combine them into a single resource that is sent to the LESS compiler on the client. When using the aggregator, the LESS loader plugin will send the request for the combined files to the aggregator, where the aggregator will aggregate the requested modules on the server and invoke the server-side LESS compiler.
If you have one or more modules that are needed by all the LESS resources in your application, you can use the
lessIncludes
config property to specify that these files be included with any requests for LESS resources. For example:When packageB/styles.less is required, the effect will be the same as if packageA/mixin.less and packageB/styles.less were required together as in the first example.
The
lessIncludes
config property can be set for the entire application by setting the property on the global require object, or for parts of the application by setting thelessIncludes
config property on a context require.Limitations
One limitation of this approach has to do with nested imports. Because the module being compiled is an aggregation of modules from different AMD packages, when a nested import is encountered, it is not clear which of the original modules the import should be relative to. The client side-compiler provides the ability to specify an array of paths to search for imports, and the server-side compiler should support this as well since it is ultimately invokes the JavaScript version of the compiler using Rhino, so we should be able to specify the paths of the original modules for the LESS compiler to search for imports. This, however, could result in shadow modules being hidden by other, similarly named modules that occur earlier in the search path.