Is there a way to configure RequireJS to always use a specific plugin, when a certain extension has been found?
I would like to avoid to manually call the plugin like require('es6!myModule')
Some sort of a-la WebPack config is what I'm looking for, but I haven't had much luck in the docs nor the web
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
plugins: ['react-hot-loader/babel'],
},
}
RequireJS is very limited the support it provides for what you are trying to do. It does not have any kind of pattern matching capabilities in the general sense of the word "pattern matching". In particular, there's no way to tell RequireJS to match files with a specific extension. If you must stick with RequireJS, and the set of modules that must get the plugin is a fixed set known when you are building your application, you could generate a map like this:
map: {
"*": {
foo: "plugin!foo",
bar: "plugin!bar",
// ...
}
}
The "*" is not actually a glob or any kind of sophisticated pattern matching. It is more like an encoded keyword that means "any module".
I have an application written in TypeScript that I must be able to run through RequireJS and be able to process through Webpack. I have a few modules that are essentially JSON data. The TypeScript compiler does not like to see plugin names in module imports. So to get everything to work smoothly I keep plugin names out of the TypeScript code code and I use a map like the above to have RequireJS use the json plugin for the modules that need it. I manage the map manually because it is only a few files.
SystemJS supports loading AMD modules and has much more sophisticated configuration facilities than RequireJS. What RequireJS calls a "plugin" is called a "loader" in SystemJS. You can use the meta option to associated a loader with a pattern of files:
{
baseURL: "lib/",
paths: {
// ...
},
meta: {
"something/*.js": {
loader: "some-loader",
}
}
}
Related
I'd like to better understand the differences between how promises are implemented in webpack. Normally, blissful ignorance was enough to get by as I mostly develop apps, but I am definately a little confused in how to properly develop a plugin/tool/lib.
In creating apps the two following approaches never caused any issues; I guess mostly cause it didn't matter
webpack.config.js - using babel-polyfill as an entry point
module.exports = {
entry: {
foo: [
'core-js/fn/promise', <-- here
'./js/index.js'
]
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
}
}
Q: In this approach, since it's a polyfill it modifies the global Promise?
webpack config - shimming using webpacks provide plugin
module.exports = {
entry: './js/index.js',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
},
plugins: [
new webpack.ProvidePlugin({
Promise: 'es6-promise' <-- here
})
]
};
Q: Does this mean that the Promise is a module only specific to the webpack bundling process? Does the transpiled ES5 code have a local copy or es6-promise? Does it patch the global Promise?
In regards to creating a jquery plugin/tool/lib which is using babel for transpilation...
webpack.config.js - using babel-plugin-transform-runtime
module.exports = {
entry: {
foo: [
'./js/start.js'
]
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
}
}
.babelrc
{
"presets": [ "es2015" ],
"plugins": ["transform-runtime"] <--here
}
start.js
require('babel-runtime/core-js/promise').default = require('es6-promise'); <--here
require('plugin');
Q: This aliases the es6-promise to the babel-runtime promise and is not global but only local to the tool?
Polyfill in webpack entry
entry: ['core-js/fn/promise', './index.js']
This has the same effect as if you imported it at the top of your entry point.
In this approach, since it's a polyfill it modifies the global Promise?
Yes, this polyfill changes the global Promise. Calling it a polyfill usually means that it patches the global built-ins, although this is not strictly adhered to. If they don't change existing APIs but only provide the functionality, they are sometimes called Ponyfills.
Webpack shimming with ProvidePlugin
new webpack.ProvidePlugin({
Promise: 'es6-promise'
})
The ProvidePlugin will import the configured module at the beginning of the module that uses it when the corresponding free variable is found. A free variable is an identifier that has not been declared in the current scope. Global variables are free variables in all local scopes.
Whenever a free Promise is encountered, webpack will add the following to the beginning of the module:
var Promise = require('es6-promise');
Does this mean that the Promise is a module only specific to the webpack bundling process?
That is correct, because ProvidePlugin is webpack specific and it's very unlikely that any other tool will respect any webpack settings.
Does the transpiled ES5 code have a local copy or es6-promise?
As with any other module, it is included once by webpack and all imports refer to that module.
Does it patch the global Promise?
It will only modify the global Promise if the imported module does it explicitly. The es6-promise you're using, does not patch the global by default as shown in Auto-polyfill.
Babel transform runtime
{
"plugins": ["transform-runtime"]
}
The babel-plugin-transform-runtime uses core-js to provide missing functionalities like Promise. As you will recall, I said that core-js modifies the global Promise. This is not true for this case, because babel uses the version that doesn't pollute the global namespace, which is in core-js/library as mentioned in the core-js README. For example:
const Promise = require('core-js/library/fn/promise');
Babel will import the core-js Promise and replace Promise with the imported variable. See also the example in babel-plugin-transform-runtime - core-js aliasing. This is essentially the same thing as webpack's ProvidePlugin except that babel does not bundle up the modules, so it's just adding the import.
This aliases the es6-promise to the babel-runtime promise and is not global but only local to the tool?
It is not global because it's just a module. Babel takes your JavaScript and outputs some other JavaScript where the configured features are transpiled to ES5. You will run or bundle the resulting JavaScript and it's practically the same as if you had written ES5 in the first place.
require('babel-runtime/core-js/promise').default = require('es6-promise');
With that you modify the export and therefore the modules will use es6-promise instead. But overwriting an export is not a good idea, especially since the imports of ES modules are immutable in the spec. Babel is currently not spec-compliant in that regard. For more details see Making transpiled ES modules more spec-compliant.
Which one should you use?
It depends on what you're doing. Apart from the difference of whether they change globals or not, you can choose whichever you prefer. For instance using babel's transform runtime allows you to use it with any tool that uses babel, not just webpack.
For a library
None.
Leave the polyfill to the application developer. But you might mention that it depends on a certain feature and when the user wants to use the library in an environment that doesn't support the feature, they have to polyfill it. It's also fairly reasonable to assume that Promises are widely supported and if an application targets older environments, they will very likely have polyfilled it already. Keep in mind that this doesn't mean that you shouldn't transpile new features / syntax. This is specifically for new functionality like Promise or String.prototype.trimLeft.
For a tool
That also depends on your definition of a tool. Let's assume a tool is a piece of software that is used by developers (e.g. webpack, eslint, etc.). In that case it is exactly the same as for any app, at the end of the day it's just another app but only targeting developers. Specifically speaking about command line tools, you should decide what minimum Node version you want to support and include anything that is needed for that, you can specify that in your package.json in the engines field.
For a plugin
Plugin is a very broad term and can be anything between a library and an app. For example a webpack plugin or loader should work as is, whereas a jQuery plugin will be part of a web app and you should treat it as a library (they should probably be called library instead of plugin). Generally you want to match the guidelines of whatever you're extending. Have a look at it and see what they are targeting. For example webpack currently supports Node verions >=4.3.0, so your plugin should too.
I am building something like a static website generator that uses webpack to build the project and create a bundle with it.
In this project, a user is able to specify custom css files. I want those css files to be bundled with the final result. The issue is, that I do not have the paths to those css files available during development, so I can't do import 'some-asset-file-provided-by-the-user.css' in the javascript code that is going to be bundled. But I have them available when calling webpack.compile(config).
I am looking for a way to inject those css files into the bundle. So far I tried various ways, such as:
const stylesheet = 'some-asset-file-provided-by-the-user.css'
require(stylesheet)
Which did not work, probably because webpack is not able to deal with this "dynamic" require. Then I used the webpack define plugin for this
/* webpack.config.js */
new webpack.DefinePlugin({
stylesheet: 'some-asset-file-provided-by-the-user.css'
}),
/* app.js */
require(stylesheet) // should be replaced by the webpack define plugin with 'some-asset-file-provided-by-the-user.css'
which also did not work. I also tried to find a way to do something like this:
{
test: /\.css$/,
loader: ExtractTextPlugin.extract(Object.assign({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
useFiles: ['file-a.css', 'file-b.css']
}
}
]
}, extractTextPluginOptions))
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
},
which also failed because apparently neither style-loader nor css-loader support this type of interaction.
How can I solve this? I am open to writing a plugin for this, but I'd rather use something existing.
The simplest way to include the CSS is by adding it to your entry point. To make this easier, you should use an array as entry point even if it's just a single file, so you can simply push the CSS.
For example:
entry: {
app: ['./src/index.js'],
// Other entries
},
In your compile script you add it to entry.app before passing it to webpack.
config.entry.app.push('./user.css');
const compiler = webpack(config);
I'd like to use Backbone with webpack in the CommonJS style, but I need to understand how to:
Tell webpack that Backbone depends on underscore
Prevent them from automatically defaulting to AMD
It seems like imports-loader might be the answer. Do I want something like this in my configuration?
module: {
loaders: [
{
test: require.resolve('_'),
loader: 'imports?_=underscore,define=>false'
},
{
test: require.resolve('backbone'),
loader: 'imports?define=>false'
}
]
},
Also, will this make it so I don't have to do var _ = require('underscore'); before I do var Backbone = require('backbone'); everywhere?
My question might be deemed a duplicate of this, but I hope to get a little more clarification than provided in the accepted answer there.
If you've installed backbone via npm, it should be required via CommonJS. When installed, NPM modules also automatically retrieve and install their dependencies in their own node_modules folder, which means you don't have to worry about providing underscore to backbone.
That's something I've struggled with for more than a year now, I don't get how we are supposed to load a JS file that contains several AMD modules at once to avoid making as many HTTP requests as there are JS files.
Since we have to define each module separately in the RequireJS config, how is it possible to load only one merged JS file containing all modules at once?
Here is my RequireJS loader: https://gist.github.com/Vadorequest/9553eaf27ac1f469cf63
In that file, what I'd like to merge are:
The requirejs libs (domReady, text, markdown)
The shared source code between the server and the client (Lang, MessageLang... View)
Because these files will increase progressively and increase the number of HTTP calls.
You will have to do this in a build step. Have you consider using RequireJS Optimizer?
The documentation is pretty solid and you will need to add a couple of parameters to your require config:
{
baseUrl: ".",
paths: {
jquery: "some/other/jquery"
},
name: "main",
out: "scripts.js"
}
This will generate one file (scripts.js) with all your files in it. There is also a bundle option if you like to group some files together requirejs bundles
I personally use gulp to take care of my build process so I actually use gulp-requirejs-bundler but the same principles apply.
I downloaded RequireJS single page app sample. In the www/lib folder, I put the XRegExp source xregexp-all.js (version 2.0.0).
In www/app/main.js, I added:
var xregexp = require('xregexp-all');
print(typeof(xregexp));
The console output is:
undefined
However, requireJS doesn't emit any error messages. Am I doing something wrong?
Yes, you are doing something wrong. Only some versions of the files distributed for XRegExp perform the required define call that defines them as RequireJS modules. The one you are using does not contain that call. You can determine this for yourself by searching for the define( string in the file.
You'll need to add a shim to your configuration so that RequireJS can load XRegExp or you'll have to use a version of the file that calls define, like for instance this one which is XRegExp 3.0.0 (still alpha). If you want to use 2.x then your config would be something like:
paths: {
xregexp: "../whatever/your/path/actualy/is/xregexp-all"
}
shim: {
xregexp: {
exports: "XRegExp",
},
}
Note that this config normalizes the module name to xregexp, so you would have to require it as xregexp, not xregexp-all. In general, I prefer not to have things like -all or .min in my module names since these may change depending on the situation.
The shim tells RequireJS that the value it should export when xregexp is required is that of the global symbol XRegExp. This is generally how you handle modules that are not AMD-aware.
A quick search for "amd", or "define" in a library's source code should be the quickest way to tell if it supports AMD scheme. If not, then it's not (doh!)
However, if the library exposes globals, then you can use RequireJS shim to load it up as well as path to tell RequireJS where to retrieve it.
// Exporting a traditional library through RequireJS
require.config({
paths : {
jaykweri : 'path/to/jQuery' // moduleName : pathToModule
},
shim : {
jaykweri : {exports : '$'} // exporting the global
}
});
// Using that library in requireJS
define(['jaykweri'],function(globl){
// globl === $
});