Webpack has a JSON loader built into it. How can I write a custom loader that doesn't try to run the built-in JSON loader on the result?
Basically, I want my loader to take in a config object (stored in a JSON file) and generate source code from that configuration, which is no longer valid JSON, it's JavaScript (which could subsequently be fed through babel-loader).
Here's a really stupid, contrived example. The source file is a JSON file, but I want the output of my loader to instead be some JS.
Loader
function compile(doc) {
return `alert(${JSON.stringify(doc.title)})`
}
function myLoader(source) {
return compile(JSON.parse(source))
}
Webpack config
rules: [
{
test: /\.json$/,
use: [
'babel-loader',
'my-loader',
],
},
],
Instead, I end up getting this error:
ERROR in ./config.json
Module parse failed: Unexpected token ' in JSON at position 0
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token ' in JSON at position 0
at JSON.parse (<anonymous>)
at JsonParser.parse (.../node_modules/webpack/lib/JsonParser.js:15:21)
As you can see from the stack trace, it's coming from webpack/lib/JsonParser.js. How can I tell Webpack not to run its built-in JSON parser in the first place, and instead delegate the processing of JSON files that match this rule to my loader?
I think I figured it out, although it appears to be an undocumented feature (at least, I couldn't find it on the configuration docs.
It looks like one of the properties you can pass in the Rule object is type. By setting it to javascript/auto, you can override the default JSON parser and have it parse the source of the file as JavaScript.
This property is in the JSON schema used by Webpack to validate the config object.
rules: [
{
test: /\.json$/,
use: [
'babel-loader',
'my-loader',
],
type: 'javascript/auto',
},
],
Related
So I have resources that I hash with copy webpack plugin.
{
from: "data/json/*.json",
to: "data/json/[name].[hash:6].json",
},
Now during runtime I need to get the access to the actual url of these json files.
What I would ideally like is to be able to fetch this url during runtime so that I can do something like
let name = "tiles";
const tileDataUrl = requireUrl(`data/json/${name}.json`);
fetch(tileDataUrl) // tileData Url here would data/json/tiles.abc34f.json
...
What I need is a method requireUrl (or whatever it might be called) which returns the actual url of the static resources with hashes during runtime.
( For anyone wondering, the hashes are used to do cache busting here)
Please and thank you :)
Assuming you're on version 5, Webpack asset modules will provide what you want without the need for copy-webpack-plugin. Webpack can recognize a require statement with expressions. Webpack will automatically included all possibly matching files for you without additional configuration. In this case you may want to watch out for optimizations where Webpack knows that name is set to "tiles". Here's the required addition to your config:
module.exports = {
module: {
rules: [
{
test: /data\/json\/.+\.json$/
type: 'asset/resource',
generator: {
// Look at https://webpack.js.org/configuration/output/#template-strings to see additional template strings.
filename: '[path][name].[hash:6][ext]'
}
}
]
}
}
Alternatively for Webpack 4 you can add file-loader as a dependency and use it with this equivalent config addition:
module.exports = {
module: {
rules: [
{
test: /data\/json\/.+\.json$/
loader: 'file-loader',
options: {
name: '[path][name].[hash:6][ext]'
}
}
]
}
}
Either way your code will now be able to work simply as follows:
let name = "tiles";
const tileDataUrl = require(`data/json/${name}.json`); // tileDataUrl will display the interpolated filename.
fetch(tileDataUrl);
I'm trying to render HTML dynamically in Vue (javascript) and I'm getting this error, does anyone know how to fix it?
Module parse failed: Unexpected token (224:36)
File was processed with these loaders:
* ./node_modules/cache-loader/dist/cjs.js
* ./node_modules/vue-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
This is the code that generates the above error:
headings: {
id: 'ID',
selected: function (h) {
return <b-form-checkbox v-model={this.checkedAll} onChange={this.selectAll}>
</b-form-checkbox>
}
}
I solved the problem by installing babel
https://github.com/vuejs/babel-plugin-transform-vue-jsx
babel.config.js
module.exports = {
presets: [
'#vue/app'
]
}
I've been trying to import some mp3 and wav files in React, but I'm getting errors on compiling which seem related to loaders.
Different syntax.
{
test: /\.(mp3|wav)$/,
use: {
loader: 'file-loader',
},
},
Uncaught Error: Module parse failed: Unexpected character '' (1:3)
You may need an appropriate loader to handle this file type.
(Source code omitted for this binary file)
import sad from './sad.mp3';
this.wrongSound = new Audio(sad);
This doesn't seem to work. However, this does without loaders at all:
this.wrongSound = new Audio('./src/sad.mp3');
I want to know why this is.
I put together a small loader so that when I require html files I will get JSX in return making use of this Htmltojsx converter
Unfortunately, since the loader just returns a string, my loader is crashing. I can verify from the loader that the string I am getting is what's expected:
import bodyHtml from './landing-body.html';
const Landing = () => (
<React.Fragment>
<h3> Landing Page </h3>
bodyHtml
...
And then the webpack build is crashing with this kind of error:
ERROR in ./Landing/landing-body.html
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
| <div>
| <h5 className="product__title"> Your Product Name </h5>
Maybe I need to put in another loader for getting this from a string to raw JSX output?
Figured it out!
Since the first loader just returns a JSX string, there's still more transpiling to be done.
What I needed to use was webpack's 'loader chaining'
So in the end my html rule looks like this:
{
test: /\.html$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
},
{
loader: path.resolve(__dirname, 'loaders/html-loader.js')
}
]
}
I guess the chained loaders are done in 'reverse order' so to speak, so the html loader first converts it to a JSX string, and then the babel-loader treats that as JSX code.
I have somewhere in my code following construction:
var getMenu = function () {
return window.fetch("portal/content/json/menu.json").then(function (data) {
return data.json();
});
};
I tried in my webpack.config.js this:
module: {
loaders: [
...
{
test: /\.json$/,
exclude: /node_modules/,
use: [
'file-loader?name=[name].[ext]&outputPath=portal/content/json'
]
},
...
]
}
Project structure:
dist
content
json
menu.json <- this is missing
src
content
json
menu.json <- source file
Question:
How can webpack copy src/content/json/menu.json to dist/content/json/menu.json ?
You're using fetch to request a JSON file and that will only happen at runtime. Furthermore, webpack only processes anything that is imported. You expected it to handle an argument to a function, but if webpack did that, every argument to a function would be considered a module and that breaks any other use for that function.
If you want your loaders to kick in, you can import the file.
import './portal/content/json/menu.json';
You can also import the JSON and use it directly instead of fetching it a runtime. Webpack 2 uses json-loader by default for all .json files. You should remove the .json rule and you would import the JSON as follows.
import menu from './portal/content/json/menu.json';
menu is the same JavaScript object that you would get from your getMenu function.
if you'd like your json to be loaded in runtime/deferred you can use awesome webpack's dynamic imports feature:
import(
/* webpackChunkName: "json_menu" */
'./portal/content/json/menu.json'
);
it will return a Promise which resolves to the module object, with "default" field containing your data. So you might want something like this (with es6 it looks really nice):
import(
/* webpackChunkName: "json_menu" */
'./portal/content/json/menu.json'
).then(({default: jsonMenu}) => {
// do whatever you like with your "jsonMenu" variable
console.log('my menu: ', jsonMenu);
});
Notice that dynamic imports require a babel plugin syntax-dynamic-import, install it with npm:
npm i babel-plugin-syntax-dynamic-import -D
Have a nice day