I just want to do a simple thing: import a JSON file and read it within a function to return some specific data of it, but it's been really hard to do.
When on dev env, it works perfectly, but when I try to do the build, the file is not fount. It's like it's not being imported. Since I'm new with Next.JS architecture, I'm not able to find a solution by myself (read a lot of pages with help, but no success).
structure
- root
- config
- labels
data.json
index.ts
index.ts
import data from './data.json';
export type HomeLabels = typeof data.in.home;
export function getHomeLabels(language: string): HomeLabels {
return data[language as keyof typeof data].home;
}
data.json
{
"in":{
"home":{
"contact_button": "Contact",
"view_all_button": "View all"
}
},
"pt-br":{
"home":{
"contact_button": "Contato",
"view_all_button": "Ver tudo"
}
}
}
error on build
TypeError: Cannot read properties of undefined (reading 'home')
at getHomeLabels (/home/runner/work/boutme/boutme/.next/server/chunks/261.js:42:43)
All the solutions that I found out was related to using API or getStaticsProps and using on component. But I really don't want it. I think that it's possible to do what I want (import json and read just on a function), but it's been hard.
I have VUE app with vue-i18n plugin.
I would like to load 'custom path' for translations when app is loaded.
The app is not loading the translations? What am I doing wrong?
File: i18n/index.js
/* eslint-disable */
import { createI18n } from "vue-i18n";
import Message from "#/localization/MyCity/en.json"
const i18n = createI18n({
// default locale
locale: "en",
// translations
messages: Message
});
export default i18n;
File: main.ts
import i18n from "./i18n";
const app = createApp(App).use(i18n)
app.mount("#app");
I tried the code above and the translations are not loading. Do you have any suggestions? This even might be the wrong approach to this problem. Do you have any other suggestion.
Topic 2:
Later on I will try and make dynamic translations based on deployment. I would like to make it fast and simple so I was thinking creating .env file with variable MY_CITY_NAME and do it like this import Message from "#/localization/${MY_CITY_NAME}/en.json".
I guess your problem might be that you are not specifying language of your Messages object.
Try this:
const i18n = createI18n({
...
messages: {
en: Message
}
});
I have the following code..
// ui.js (generated by rollup
import Vue from 'vue';
import VueRouter from 'vue-router';
(()=>{
console.log("Wow it actually works");
Vue.use(VueRouter);
const routes = [
{
path: '/',
component: Viewport
}
];
const router = new VueRouter({
mode: "history",
routes: routes
});
window.app = new Vue({ router });
window.app.$mount('#jg-app');
})();
<script src="ui.js" type="module"> </script>
The problem is when I run this I get...
Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
This leads me to believe I need a "fat" js that includes dependencies.
I also want to keep everything in es6 modules and avoid introducing say babel.
Is there a way to do this using rollup?
Update
Tried this...
import Vue from "./vue";
But then I get...
Error: Could not resolve './vue' from src/index.js
As far as I can tell this is not possible. I instead had to move the import from the ui project to the server project and create a static js file that looked like this...
//client
import Vue from "./vue"
let app = new Vue(...);
app.$mount('#jg-app');
and import the esm.browser version
// server
app.use('/vue', express.static(__dirname + '/node_modules/vue/dist/vue.esm.browser.js'));
// template
script(src="/main.js" type="module")
Now Vue is working, however, dependencies like Vue-Router appear to not have this es.browser style file.
This is not a solution, it's a workaround
The below rollup config is not esm, it's just a way to create a bundle with dependencies included.
You get one minified browser-compatible JS file.
Here's my working example rollup.config.js (you should replace input: 'src/index.js' with your web app entry point and output.file with a location for the generated bundle):
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import builtins from 'rollup-plugin-node-builtins';
import babel from 'rollup-plugin-babel';
import visualizer from 'rollup-plugin-visualizer';
import { terser } from "rollup-plugin-terser";
const browserPlugins = [
resolve({browser: true}), // so Rollup can properly resolve cuid
babel({
exclude: 'node_modules/**',
babelrc: false,
presets: ['es2015-rollup'],
}),
// builtins(),
commonjs(),
visualizer(),
terser(),
]
export default [
// browser-friendly UMD build
{
// external: Object.keys(globals),
input: 'src/index.js',
output: {
name: 'thinflux',
file: './dist/browser/thinflux.min.js',
format: 'umd'
},
plugins: browserPlugins,
}
];
One more thing: express should statically serve the output.file path, not your source files
I have one json file at root:
config.json
{ "base_url": "http://localhost:3000" }
and in my service class, I want to use it in this way:
private productsUrl = config.base_url + 'products';
I've found a ton of posts with either solutions that require a http.get request to load that one file to get that one variable or outdated solutions for angular.js (angular 1)
I cant believe there isnt an easier way to include this file that we already have in place without having to make an additional request to the server.
In my opinion, I would have expected that at least the bootstrapping function would be able to provide this kind of functionality, something like:
platformBrowserDynamic().bootstrapModule(AppModule, { config: config.json });
btw, this works, but its not the ideal solution:
export class Config {
static base_url: string = "http://localhost:3004/";
}
and the use it where you need it:
private productsUrl = Config.base_url + 'products';
Its not ideal, because I will have to create the class (or replace properties) in a build script. (exactly what I was thinking to do with the config.json file).
I still prefer the config.json file approach, since it would not be intrusive with the TypeScript compiler. Any ideas how to do are welcome and really appreciated!
This link explains how to use System.js to load json files in an angular app.
Special thanks to #eotoole that pointed me in the right direction.
If the link above is not clear enough, just add a map into the System.js conf. like this:
map: { 'plugin-json': 'https://unpkg.com/systemjs-plugin-json' }*
*(using external package)
or
map: { 'plugin-json': 'plugin-json/json.js' }**
**if you download the plugin from:
official system.js plugin
now I can use:
const config = require('./config.json');
anywere in my app.
and since it is official from the "systemjs" - guys, I feel comfortable using it to load app settings like base_url or other endpoints.
Now I need to figure out how to encapsulate this logic for testing purposes. Maybe requiring the file in its own class and replacing the values for the specific test case.
Are you using webpack? If you are, and you can just do
const config = require('./config.json');
#Injectable()
export class MyService {
private config:any = config;
....
}
in your webpack config you will need the json-loader
...
module: {
...
loaders: [
...
{
test: /\.json$/,
loaders: ["json-loader"]
},
...
]
}
...
The typescript compiler works fine when I import a json file using
const tasks = require('./tasks.json')
However, when I run tsc, the output directory does not contain no tasks.json file, causing a runtime error.
Is there a way to tell the compiler that it should copy all json files, or should I manually copy/paste all my json files into the dist directory ?
my tsc compilerOptions currently reads
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"sourceMap": true,
"noImplicitAny": true,
"removeComments": false,
"outDir": "./dist/",
"sourceMap": true,
"pretty": true,
"noImplicitThis": true,
"strictNullChecks": true,
"sourceMap": true
},
Thanks !
Problem
For people wanting to copy all JSON files, it's really difficult in TypeScript. Even with "resolveJsonModule": true, tsc will only copy .json files which are directly referenced by an import.
Here is some example code that wants to do a dynamic runtime require(). This can only work if all the JSON files have been copied into the dist/ folder, which tsc refuses to do.
// Works
import * as config from './config.default.json';
const env = process.env.NODE_ENV || 'development';
const envConfigFile = `./config.${env}.json`;
// Does not work, because the file was not copied over
if (fs.existsSync(envConfigFile)) {
const envConfig = require(envConfigFile);
Object.assign(config, envConfig);
}
Solution 1: Keep json files outside the src tree (recommended)
Assuming you have /src/ and /dist/ folders, you could keep your JSON files in the project's / folder. Then a script located at /src/config/load-config.ts could do this at runtime:
const envConfig = require(`../../config.${env}.json`);
// Or you could read manually without using require
const envConfigFile = path.join(__dirname, '..', '..', `config.${env}.json`);
const envConfig = JSON.parse(fs.readFileSync(envConfigFile, 'utf-8'));
This is the simplest solution. You just need to make sure the necessary config files will be in place in the production environment.
The remaining solutions will deal with the case when you really want to keep the config files in your src/ folder, and have them appear in your dist/ folder.
Solution 2: Manually import all possible files
For the above example we could do:
import * as config from './config.default.json';
import * as testingConfig from './config.testing.json';
import * as stagingConfig from './config.staging.json';
import * as productionConfig from './config.production.json';
This should cause the specified json files to be copied into the dist/ folder, so our require() should now work.
Disadvantage: If someone wants to add a new .json file, then they must also add a new import line.
Solution 3: Copy json files using tsc-hooks plugin (recommended)
The tsc-hooks plugin allows you to copy all files from the src tree to the dist tree, and optionally exclude some.
// Install it into your project
$ yarn add tsc-hooks --dev
// Configure your tsconfig.json
{
"compilerOptions": {
"outDir": "dist"
},
// This tells tsc to run the hook during/after building
"hooks": [ "copy-files" ]
// Process everything except .txt files
"include": [ "src/**/*" ],
"exclude": [ "src/**/*.txt" ],
// Alternatively, process only the specified filetypes
"include": [ "src/**/*.{ts,js,json}" ],
}
I found it tsc-hooks announced here.
Solution 4: Copy json files using an npm build script (recommended)
Before tsc-hooks, we could add a cpy-cli or copyfiles step to the npm build process to copy all .json files into the dist/ folder, after tsc has finished.
This assumes you do your builds with npm run build or something similar.
For example:
$ npm install --save-dev cpy-cli
// To copy just the json files, add this to package.json
"postbuild": "cpy --cwd=src --parents '**/*.json' ../dist/",
// Or to copy everything except TypeScript files
"postbuild": "cpy --cwd=src --parents '**/*' '!**/*.ts' ../dist/",
Now npm run build should run tsc, and afterwards run cpy.
Disadvantages: It requires an extra devDependency. And you must make this part of your build process.
Solution 5: Use js files instead of json files
Alternatively, don't use .json files. Move them into .js files instead, and enable "allowJs": true in your tsconfig.json. Then tsc will copy the files over for you.
Your new .js files will need to look like this: module.exports = { ... };
I found this idea recommended here.
Note: In order to enable "allowJs": true you might also need to add "esModuleInterop": true and "declaration": false, and maybe even "skipLibCheck": true. It depends on your existing setup.
And there is one other concern (sorry I didn't test this):
Will tsc transpile your config files if they are not all statically referenced by other files? Your files or their folders may need to be referenced explicitly in the files or include options of your tsconfig.json.
Solution 6: Use ts files instead of json files
Sounds easy, but there are still some concerns to consider:
Your config files will now look something like this: const config = { ... }; export default config;
See the note above about files / include options.
If you load the config files dynamically at runtime, don't forget they will have been transpiled into .js files. So don't go trying to require() .ts files because they won't be there!
If someone wants to change a config file, they should do a whole new tsc build. They could hack around with transpiled .js files in the dist folder, but this should be avoided because the changes may be overwritten by a future build.
Testing
When experimenting with this, please be sure to clear your dist/ folder and tsconfig.tsbuildinfo file between builds, in order to properly test the process.
(tsc does not always clean the dist/ folder, sometimes it just adds new files to it. So if you don't remove them, old files left over from earlier experiments may produce misleading results!)
In tsconfig.json, add
{
"compilerOptions": {
"resolveJsonModule": true,
},
"include": [
"src/config/*.json"
]
}
Notice that it won't copy those json files which are required. If you need to dynamically require some json files and need them to be copied to dist, then you need to change from, for example,
return require("some.json") as YourType
to
return (await import("some.json")) as YourType
.
In typescript 2.9+ you can use JSON files directly and it automatically copied to dist directories.
This is tsconfig.json with minimum needed configuration:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop" : true,
"module" : "commonjs",
"outDir" : "./dist",
"resolveJsonModule" : true,
"target" : "es6"
},
"exclude" : [
"node_modules"
]
}
Then you can create a json file.
{
"address": "127.0.0.1",
"port" : 8080
}
Sample usage:
import config from './config.json';
class Main {
public someMethod(): void {
console.log(config.port);
}
}
new Main().someMethod();
If you don't use esModuleInterop property you should access your json properties encapsulated in default field. config.default.port.
The typescript compiler works fine when I import a json file using
const tasks = require('./tasks.json')
TypeScript wouldn't complain about this as long as you have a global require() function defined, for example using node.d.ts. With a vanilla setup you would actually get a compile error that require is not defined.
Even if you've told TypeScript about a global require function it just sees it as a function that's expected to return something, it doesn't make the compiler actually analyze what the function is requiring ("tasks.json") and do anything with that file. This is the job of a tool like Browserify or Webpack, which can parse your code base for require statements and load just about anything (JS, CSS, JSON, images, etc) into runtime bundles for distribution.
Taking this a little further, with TypeScript 2.0 you can even tell the TypeScript Compiler about module path patterns that will be resolved and loaded by a bundler (Browserify or Webpack) using wildcard (*) module name declarations:
declare module "*.json" {
const value: any;
export default value;
}
Now you can import your JSON in TypeScript using ES6 module syntax:
import tasks from "./tasks.json";
Which will not give any compile error and will transpile down to something like var tasks = require("./tasks.json"), and your bundler will be responsible for parsing out the require statements and building your bundle including the JSON contents.
you can include this into your build script && ncp src/res build/res, will copy the files directly to your outDir
You can always get an absolute path to your project, with typescript code. To do it just read the JSON file not by the required keyword but with the help of the fs module. In a path of file use process.cwd() to access typescript project directory:
import * as fs from 'fs';
const task: any = JSON.parse(fs.readFileSync(`${process.cwd()}/tasks.json`).toString());
To make it work correctly you may need to change your running script to node dist/src/index.js where you specify a dist folder in the path.