I need to dereference (swap out JSON References) when importing JSON or YAML files directly into Gatsby pages. Gatsby is pretty good at importing JSON files directly (probably due to some underlying webpack loader), but it doesn't seem to handle JSON refs, a lá "$ref": "../something.json.
For example:
parent.json
{
"someRef" {
"$ref": "./child.json"
}
}
child.json
{
"foo": "bar"
}
index.js
import {SomeJson} from './parent.json'
console.log(SomeJson)
// Expected output: {someRef: {foo: bar}}
// Actual output: {someRef: {$ref: './child.json'}}
I figured that perhaps the default JSON loader being used by Gatsby doesn't allow for JSON References. Fine. But couldn't the webpack configuration be extended to try and add this functionality? Here's my attempt:
gatsby-node.js
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
module: {
rules: [
{
test: /\.json$/,
use: [
{
loader: '#cloudflare/json-schema-ref-loader'
}
]
}
]
},
})
}
The #cloudflare/json-schema-ref-loader library is a webpack loader designed to follow refs. so I figured this could be used.
When running gatsby develop, the build fails with:
Unexpected token m in JSON at position 0 while parsing near 'module.exports = {
...'
error Generating development SSR bundle failed
Any help would be greatly appreciated.
Related
Looking for some help to understand what is going on here.
The Problem
We are using a translation service that requires creating JSON resource files of copy, and within these resource files, we need to add some specific keys that the service understands so it knows what should and should not be translated.
To do this as simple as possible I want to import JSON files into my code without them being tree shaken and minified. I just need the plain JSON file included in my bundle as a JSON object.
The Solution - or so I thought
The developers at the translation service have instructed me to create a webpack rule with a type of assets/source to prevent tree shaking and modification.
This almost works but the strange thing is that the JSON gets added to the bundle as a string like so
module.exports = "{\n \"sl_translate\": \"sl_all\",\n \"title\": \"Page Title\",\n \"subtitle\": \"Page Subtitle\"\n}\n";
This of course means that when I try and reference the JSON values in my JSX it fails.
Test Repo
https://github.com/lukehillonline/nextjs-json-demo
NextJs 12
Webpack 5
SSR
Steps To Reproduce
Download the test repo and install packages
Run yarn build and wait for it to complete
Open /.next/server/pages/index.js to see the SSR page
On line 62 you'll find the JSON object as a string
Open .next/static/chunks/pages/index-{HASH}.js to see the Client Side page
If you format the code you'll find the JSON object as a string on line 39
Help!
If anyone can help me understand what is going wrong or how I can improve the webpack rule to return a JSON object rather than a string that would be a massive help.
Cheers!
The Code
next.config.js
module.exports = {
trailingSlash: true,
productionBrowserSourceMaps: true,
webpack: function (config) {
config.module.rules.push({
test: /\.content.json$/,
type: "asset/source",
});
return config;
},
};
Title.content.json
{
"sl_translate": "sl_all",
"title": "Page Title",
"subtitle": "Page Subtitle"
}
Title.jsx
import content from "./Title.content.json";
export function Title() {
return <h1>{content.title}</h1>;
}
pages/index.js
import { Title } from "../components/Title/Title";
function Home({ dummytext }) {
return (
<div>
<Title />
<p>{dummytext}</p>
</div>
);
}
export const getServerSideProps = async () => {
const dummytext = "So we can activate SSR";
return {
props: {
dummytext,
},
};
};
export default Home;
Here is my code:
import { Map as LeafletMap, TileLayer } from 'react-leaflet';
function Map() {
return (
<div className="map">
<LeafletMap>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'/>
</LeafletMap>
</div>
But I got an error saying Attempted import error: 'Map' is not exported from 'react-leaflet' (imported as 'LeafletMap'). I've tried changing the 'import { Map' to 'import { MapContainer' but still won't work. Instead I got another error saying:
./node_modules/#react-leaflet/core/esm/path.js 10:41
Module parse failed: Unexpected token (10:41)
File was processed with these loaders:
* ./node_modules/babel-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
| useEffect(function updatePathOptions() {
| if (props.pathOptions !== optionsRef.current) {
> const options = props.pathOptions ?? {};
| element.instance.setStyle(options);
| optionsRef.current = options;
I also tried changing the react-leaflet version from ^3.2.1 to ^2.7.0 and leaflet from ^1.7.1 to ^1.6.0. But still no luck. Any solutions?
You'll have to explicitly transpile react-leaflet because the maintainer is not understanding they should change their transpilation target to a lower version in order to more fully support the nullish coalescing operator: https://github.com/PaulLeCam/react-leaflet/issues/877#issuecomment-841871904
You can try adding this to your babel config
{
"plugins": ["#babel/plugin-proposal-nullish-coalescing-operator"]
}
And then make sure you transpile node_modules/react-leaflet during your build. If you are already using #babel/preset-env then you only need to make sure you're transpiling react-leaflet during the build. If you're using webpack you can do something like
module: {
rules: [
{
test: /\.jsx?$/,
exclude: filename => {
return /node_modules/.test(filename) && !/react-leaflet/.test(filename)
},
use: ['babel-loader']
}
]
}
I am using both vue single-file components and separating of markup and logic to .pug and .ts files respectively. If you interesting why I don't unify is please see the comments section.
Problem
import template from "#UI_Framework/Components/Controls/InputFields/InputField.vue.pug";
import { Component, Vue } from "vue-property-decorator";
console.log(template);
#Component({
template,
components: {
CompoundControlBase
}
})
export default class InputField extends Vue {
// ...
}
In development building mode exported template is correct (I beautified it for readability):
<CompoundControlBase
:required="required"
:displayInputIsRequiredBadge="displayInputIsRequiredBadge"
<TextareaAutosize
v-if="multiline"
:value="value"
/><TextareaAutosize>
</CompoundControlBase>
In production mode, my markup has been lowercased. So, the console.log(template) outputs:
<compoundcontrolbase
:required=required
:displayinputisrequiredbadge=displayInputIsRequiredBadge
<textareaautosize
v-if=multiline
:value=value
></textareaautosize>
</compoundcontrolbase>
Off course, I got broken view.
Webpack config
const WebpackConfig = {
// ...
optimization: {
noEmitOnErrors: !isDevelopmentBuildingMode,
minimize: !isDevelopmentBuildingMode
},
module: {
rules: [
{
test: /\.vue$/u,
loader: "vue-loader"
},
{
test: /\.pug$/u,
oneOf: [
// for ".vue" files
{
resourceQuery: /^\?vue/u,
use: [ "pug-plain-loader" ]
},
// for ".pug" files
{
use: [ "html-loader", "pug-html-loader" ]
}
]
},
// ...
]
}
}
Comments
To be honest, I don't know why we need ? in resourceQuery: /^\?vue/u, (explanations are welcome).
However, in development building mode above config works property for both xxxx.vue and xxxx.vue.pug files.
I am using below files naming convention:
xxx.pug: pug file which will not be used as vue component template.
xxx.vue.pug: pug file which will be used as vue component template.
xxx.vue: single-file vue component.
xxx.vue.ts: the logic of vue component. Required exported template from xxx.vue.pug as in InputField case.
Why I need xxx.vue.ts? Because of this:
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
Neither public methods/fields nor non-default methods are visible for TypeScrpt xxx.vue files. For the common (non-applied) components, I can't accept it.
Repro
🌎 GitHub
Step 1: Install dependencies
npm i
Step 2: Let's check the development building first
npm run DevelopmentBuild
In line 156 of DevelopmentBuild\EntryPoint.js, you can check that below pug template:
Alpha
Bravo OK
has been compiled properly:
Step 3: Problem on production build
npm run ProuductionBuild
You can find the lowercased tags in the column 13:
You can also open index.html in your browser and check the console.log() output with compiled TestComponent.
The problem is the "html-loader". It has the option minimize set to true in production mode (html-loader/#minimize).
I had a similar problem in angular and had to unset some options like (see for reference html-minifier-terser#options-quick-reference).
// webpack.config.js
{
test: /\.pug$/u,
oneOf: [
// for ".vue" files
{
resourceQuery: /^\?vue/u,
use: [ "pug-plain-loader" ]
},
// for ".pug" files
{
use: [ "html-loader", "pug-html-loader" ]
}
],
options: {
minimize: { // <----
caseSensitive: false // <----
} // <----
}
},
I have a YAML file with a few translations. I need to transform these files into a JSON file. I've tried using yaml-import-loader and json-loader but I get an error.
Here's my setup:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractEnglish = new ExtractTextPlugin('lang/en.js');
module.exports = {
entry: [
'./src/locales/application.en.yml',
],
output: {
filename: 'english.js',
},
module: {
strictExportPresence: true,
rules: [
{
test: /\.en\.yml$/,
use: extractEnglish.extract({
use: [
// { loader: 'json-loader' },
{
loader: 'yaml-import-loader',
options: {
output: 'json',
},
}],
}),
},
],
},
plugins: [
extractEnglish,
],
};
And the error I get:
Users/xxx/Documents/Project/node_modules/extract-text-webpack-plugin/dist/index.js:188
chunk.sortModules();
^
TypeError: chunk.sortModules is not a function
at /Users/xxx/Documents/Project/node_modules/extract-text-webpack-plugin/dist/index.js:188:19
Same error whether or not the json-loader is commented or not.
I really don't understand what is going wrong.
Versions:
"webpack": "2.6.1",
"extract-text-webpack-plugin": "^3.0.0",
"json-loader": "^0.5.7",
Not sure if this will help your situation but I recently found a solution to my i18n loading problem. I do this to extract YAML into JSON files upfront as I use angular-translate and needed to load files dynamically and on-demand. I avoid extract-text-webpack-plugin and use only loaders: file-loader and yaml-loader.
First I setup the import of my .yaml files near the beginning of source (in my case a specific chain of import files for webpack to process)
import "./i18n/en.user.yaml";
I updated webpack config to translate YAML to JSON and have it available to load dynamically (everything originates from my 'src' directory, hence the context):
rules: [{
test: /.\.yaml$/,
use: [{
loader: 'file-loader',
options: {
name: '[path][name].json',
context: 'src'
}
},{
loader: 'yaml-loader'
}]
}]
This will translate my yaml file(s) and export them to my public directory, in this case at '/i18n/en.user.json'.
Now when angular-translate uploads my configured i18n settings via $http on-demand, it already has the parsed YAML and avoids having to parse it with js-yaml (or similar) on the front end.
A relatively old question, but I found it while searching for a solution to the same problem, so I thought it worth to chip in.
If you're not really using translation files in your code (i.e. you never import and use them directly) then using a Webpack loader is not the most elegant solution (you'd be forced to import them just so that the loader could be triggered and perform the conversion).
An alternative would be to use the CopyWebpackPlugin instead: it supports a transform option, which takes a function receiving the content of the file as a Buffer.
With a YAML parser (like js-yaml) as an additional dependency, adding this to your Webpack configuration would work:
const yaml = require('js-yaml');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
// OTHER WEBPACK CONFIG HERE
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: 'i18n/**/*',
to: 'i18n/[name].json',
transform(content) {
return Buffer.from(
JSON.stringify(
yaml.load(content.toString('utf8'), {
schema: yaml.JSON_SCHEMA
})
),
'utf8'
)
}
}
]
})
]
}
The i18n folder in the above example would contain your .yml translations.
The Copy plugin would load them, convert them to JSON, and save them in the output folder under i18n/ (as specified by the to option).
I am new to Webpack and I am having a hard time to understand how I can access the content of my json file being added in entry and loaded using json-loader:
entry :
en: {
`${config.basePaths.tmp}script.js`,
`${config.basePaths.tmp}en.json`
}
},
output: {
publicPath: '/js/',
path: `${config.paths.scripts.dist}`,
filename: `[name].script.js`
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
},
{
test: /\.coffee$/,
loader: 'coffee-loader'
},
{
test: /\.json$/,
loader: 'json-loader'
}
]
}
The object is added to my output file (en.script.js) but I have no clue how to access it... It looks like this:
function(module, exports) {
module.exports = {
"en": {
"title": "I love webpack",
"something": "else"
}
};
}
Please help me.
You should not put a JSON file as the entry point. Entry points are meant to be the starting point of your app and if you specify multiple files per entry point it's just to include them in the same bundle, but they don't really interact with each other (see Concepts - Entry Points).
Instead with webpack you can import files that are not JavaScript, such as .json, and if you have an appropriate loader it will turn that into valid JavaScript which you can use just as any other JavaScript you import. So in your script.js (your entry point) you can import the JSON with:
import enJson from './en.json';
Now you can use enJson as you want, which contains the JavaScript representation of en.json. And by the way remove that from your entry, because webpack will figure out which files are used by looking what is imported from your entry point (and all its dependencies).
entry : {
en: `${config.basePaths.tmp}script.js`,
},