I'm building a custom Webpack loader. What the loader does is unimportant, but it transforms JSON in some way and uses paths from the JSON in order to resolve certain other details. In my loader.js I need a way of getting the original path of the JSON file being loaded such that I can resolve other paths properly.
Take this simple loader and config:
loader.js
module.exports = function (source) {
/* Do some file lookups based on source and modify source */
this.callback(null, source)
}
webpack.config.js
module.exports = {
/* ... */
module: {
rules: [
{
test: /\.json$/i,
use: ['loader'],
},
],
},
};
The loader is working (being used), but any business logic I add needs to be path-aware such that it can do lookups on the file system.
Because this is a JSON loader, the source var in the loader function is passed as raw JSON content, with no details about which file the JSON was loaded from. How does one go about getting path information from within the loader such that I can perform other file lookups based on the content from source?
It turns out the property I was looking for is available via the execution context property this, as resourcePath.
module.exports = function (source) {
console.log('The original file was here:', this.resourcePath)
this.callback(null, source)
}
Here is the (rather sparse) documentation.
If anybody can think of a better/more future-proof way of getting this path then feel free to comment or post another answer.
Related
I am trying to optimise my Webpack bundle and I have identified a large chunk of data that is being bundled from one of my dependencies. The dependency is world-countries, which is just a huge JSON array of objects containing country data. I am however only using a fraction of this data in my app (a simple React app).
So what I am thinking is that I want to add something into my Webpack config that will effectively let me map the large objects down into just the properties I am using, and avoid having all the rest of the data end up in my bundle.
My only idea currently on how to do this would be to write a node script that runs postinstall or prebuild that imports the module, maps it and saves it back out to disk in a new JSON file. Then that JSON file is what my app imports.
I'm looking for any advice or ideas on the best way to implement this, preferably in a way that doesn't affect my apps code and is just part of the Webpack config.
You can write a custom loader which will be applied on the Jason import, inside it filter out what you need.
i am using DefinePlugin now to create only the JSON I need. In my example i am using the countries from country-data:
Webpack config:
import { countries } from 'country-data';
...
plugins: [
new webpack.DefinePlugin({
COUNTRIES: webpack.DefinePlugin.runtimeValue(function() {
return JSON.stringify(countries.all.filter(function (c) {
// Has "United Kingdom" twice, #see https://github.com/OpenBookPrices/country-data/issues/72
var assigned = c.status === 'assigned';
// cleanup JSON
['countryCallingCodes', 'languages', 'currencies', 'emoji', 'ioc', 'alpha3', 'status'].forEach(function (key) {
delete c[key];
});
return assigned;
}));
}),
}),
]
You then can use COUNTRIES in your code!
We load files dynamically i.e., we don't know which files will be loaded until runtime. At the same, for faster loading, we'd like to put related files in the same chunk.
How can I do that with webpack?
This is what we have and it's failing with a 404 error (1.1.bundle.js not found)
This is what webpack.config looks like:
entry: {
main: //...,
related_files: [ //should create chunk for file1 and file2?
'./file1.js',
'./file2.js'
]
},
This is what the code to dynamically load the files looks like:
var dynamicFileName = //...
require.ensure([], function (require) {
//should dynamically load the chunk containing dynamicFileName?
//fails with 'file1.js' or 'file2.js'
var modImpl = require(dynamicFileName);
//...
});
Update 1: the error message is caused by not configuring output.publicPath. However, I never created 1.1.bundle.js. It seems to be ignoring the entry point.
Update 2: even after fixing output.publicPath, it's unable to load a dynamically generated filename. So it seems that webpack cannot handle this.
By default, webpack tries to bundle all the code in a single file. If you're using code from file1.js/file2.js in main entry point, webpack will bundle contents of all the files in main.js, and second entry point related_files will output only file1/file2 contents.
Webpack handles this situation by using CommonsChunkPlugin, your config must look like this:
entry: {
main: //...,
related_files: ['./file1.js','./file2.js']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('related_files', 'related_files.js')
]
Second part of the question is that webpack parses require statement, and outputs 1.1.bundle.js - the dynamic module, that can be loaded with require in the code. In your case, dynamicFileName = 'related_files', not file1/file2.
Please see http://webpack.github.io/docs/code-splitting.html#split-app-and-vendor-code
I use WebJars to manage my JavaScript dependencies which means that most of the libraries I use are outside the Require.js base path. The config is created automatically and it works for most libraries. I only have problems with those libraries that call require() to load some inner dependencies (usually just external files) - for example when.js.
This is the require.js config generated for when library:
requirejs.config({"paths":{"when":["/webjars/when-node/3.5.2/when","when"]}});
This file loads properly. But the problem is that it tries to load further files:
...
var timed = require('./lib/decorators/timed');
var array = require('./lib/decorators/array');
var flow = require('./lib/decorators/flow');
var fold = require('./lib/decorators/fold');
...
I would expect require.js to use the location of when.js to determine the correct locations of the other required files, i. e.:
/webjars/when-node/3.5.2/lib/decorators/timed.js
But unfortunately require.js uses instead the location of the main.js file as the base path which obviously results in a lot of 404 errors and the application crashes.
How can I tell require.js to look in the correct subdirectory?
So according to the RequireJS documentation this seems to be the correct behavior. Unlike CommonJS, every require() call is resolved using the base path (it is relative to the base path, not to the location of the file it is called in).
The only way to get around this (as far as I know) is to configure the dependency as a package. The package location is then used for path resolution instead of the general base path. For the mentioned when package, the configuration should look something like this:
requirejs.config({
packages: [
{ name: 'when', location: '/path/to/when', main: 'when' }
]
});
To fix this problem automatically for all my current or future dependencies, I've replaced the Scala Play Framework loading script with my own solution which loads all WebJars as separate packages.
var require = {
callback : function () {
var packages = [];
var shim = {};
[
#for(webJarJson <- org.webjars.RequireJS.getSetupJson(routes.WebJarAssets.at("").url).values()) {
#Html(webJarJson.toString),
}
].forEach(function (webjar) {
if (webjar.paths) {
for (var name in webjar.paths) {
if (webjar.paths.hasOwnProperty(name)) {
packages.push({
name: name,
location: webjar.paths[name][0].replace(/\/[^\/]+$/, ""),
main: webjar.paths[name][1]
});
}
}
}
if (webjar.shim) {
for (var name in webjar.shim) {
if (webjar.shim.hasOwnProperty(name)) {
shim[name] = webjar.shim[name];
}
}
}
});
requirejs.config({
packages: packages,
shim: shim
});
}
}
I'd like to have 2 requirejs paths pointing to the same module:
var require = {
paths: {
"hardPath" : "file",
"alias" : "file"
}
}
When I run my app I get a load timeout error for "alias". If my app's js files only reference one of "hardPath" or "alias" but not the other, it works fine. But if I have js files that reference both of these, I get the load timeout. Is there some reason that require.js does not allow this?
The API that RequireJS uses for this is map. You can configure it such that when any of your modules request 'alias' they are automatically given 'hardPath':
require.config({
// paths, shim, etc.
// and now remap requests for the wrong module name to the right one
map: {
'*': {
'alias': 'hardPath'
}
}
});
From the doc linked above:
In addition, the paths config is only for setting up root paths for module IDs, not for mapping one module ID to another one.
I am currently working myself into requirejs, to do this I started to write a small package. The package consists of one main.js, the entry point for the application and several other modules that will be loaded into main.js through a call to require.
The package has several options that can be configured during runtime, to ease maintenance I have a central configuration.js that is loaded whenever needed. The contents of configuration.js is just an object that maps key to value, no functionality or anything. Just a key-value map.
Now requirejs allows to pass options to packages, the configuration is done through the requirejs config. The problem I have is, that it is only available in the main.js and not in other modules in the package, also I could not find a way to set default values unless I hardcode them into the main.js.
My current approach (the one with the configuration.js) is to merge the configuration given to requirejs with the contents of my configuration.js in the main.js.
main.js
define(function(require, exports, module) {
var $ = require('jquery'); // Could be any library that offers an "extend"
// feature that allows "deep" copies.
var config = require('configuration'); // Load configuration file
$.extend(true, config, module.config()); // Overwrite default values with
// set values
var otherfile = require('anyotherfile);
otherfile();
});
anyotherfile.js
define(function(require, exports, module) {
var config = require('config'); // Loads configuration, package-wide
// options are set
// module.config() will be empty. Unless I explicicy specify the
// configuration for this one module in the "requirejs.config"
return function() {
// Something that can be configured
};
});
This way of setting configuration options makes me dependent on an additional library or my own implementation of an extend functionality (it's not much code, but still more code than no code) plus there are two points where I have to look for possible configuration mistakes which makes it harder to find errors.
So now I am searching for a better way to pass a package-wide configuration all while trying to avoid more than one point of failure. (This might be hard cause somewhere I have to store the default values if I don't want to hardcode them.) And of course the solution should not depend on any additional libraries or my own extend functionality.
After talking with jrburke about the problem the best solution is to do the extending of the config array in the configuration.js.
Example of the file configuration.js
define(function(require, exports, module) {
var $ = require('jquery'); // need jQuery for extend
var config = {
key: value,
...
};
return $.extend(true, config, module.config());
});
So there is no more need to do this in the main.js.
The only disadvantage of this approach is that in the requirejs.config you have to address
package/configuration and not package.
Example of the requirejs.config
requirejs.config({
config: {
"package/configuration": {
key: value
}
}
});