That is the flow I want:
1 - Have a normal HTML file on one folder (HTML folder)
2 - Take this file, convert is to EJS file
3 - Insert the data I want on the EJS file
4 - Convert that EJS file back to HTML file, so now I will have the "same" HTML file, but now with all the data I want.
5 - Store this HTML on AWS S3 ( Or just locally, for now )
Render an EJS file into HTML markup
Source: Render EJS File with Node.js
EJS is a templating language that uses JavaScript to generate HTML. This post will illustrate how to use Node.js with TypeScript to render an EJS file into HTML markup. Please make sure you have Node.js and npm installed first. If you are unfamiliar with Typescript please read my post describing how to compile TypeScript with npm.
EJS
Begin by creating a new EJS file named index.ejs. This file will be the template used to generate index.html. If the model is passed into the template it will render the content as a paragraph.
<!-- Sample Page -->
<h1>Sample Page</h1>
<% if (model) { %>
<%= model.content %>
<% } %>
package.json
If you don't already have a package.json created you can create one by running the command npm init and following the prompts.
You will need your package.json to include these packages:
{
"name": "package-name-goes-here",
"version": "0.0.0",
"devDependencies": {
"#types/ejs": "^2.6.2",
"#types/node": "^11.9.4",
"ejs": "^2.6.1",
"typescript": "^3.3.3333"
}
}
You can also copy the devDependencies section and run the command npm install instead of installing one at a time.
Node.js
Create a new TypeScript file named render.ts. Then add the following code to import the modules that we will use.
//imports
import util = require("util");
import fs = require("fs");
import ejs = require("ejs");
//promisify
const mkdir = util.promisify(fs.mkdir);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
The first import is the util module so that we can use the promisify function. Then import the fs module for file system access. Before using three of the functions from the fs module we can promisify them allowing for the use of async/await instead of nested callbacks. The last is for EJS, and since the render file function returns a promise by default we do not need to use promisify.
Below the import statements add an async function named render. This is where the HTML output will be generated and written to a file named index.html. It needs to be marked as an async function so that the keyword await can be used. Then make sure to call the function so the code that is about to be added will execute.
async function render() {
try {
} catch (error) {
console.log(error);
}
}
render();
Before rendering our EJS file we will need a folder to put the output. So add the following to our render function:
await mkdir("dist", { recursive: true });
This will create a new directory named dist where the html output will be saved. By passing the recursive property we can ensure parent folders are created even if none are necessary. After creating the dist folder we can use EJS to render the index.ejs template to HTML. The resulting HTML string is then written to a file named index.html in the dist folder.
At this point your index.ts file should look like this:
//imports
import util = require("util");
import fs = require("fs");
import ejs = require("ejs");
//promisify
const mkdir = util.promisify(fs.mkdir);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
async function render() {
try {
//create output directory
await mkdir("dist", { recursive: true });
//render ejs template to html string
const html = await ejs
.renderFile("index.ejs", { model: false })
.then((output) => output);
//create file and write html
await writeFile("dist/index.html", html, "utf8");
} catch (error) {
console.log(error);
}
}
render();
In order to run this script we need to add a tsconfig.json file to configure the TypeScript compiler. This will compile the TypeScript into JavaScript so that it can be used by node.js. Add the tsconfig file to the same folder as the render.js script.
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"rootDir": "./",
"outDir": "./dist",
"sourceMap": true
},
"include": ["render.js"]
}
We also need to add a script to the package.json file created earlier. This script will compile render.ts and then run it using node. Your package.json should look like this:
{
"name": "package-name-goes-here",
"version": "0.0.0",
"scripts": {
"render": "tsc && node dist/render.js"
},
"devDependencies": {
"#types/ejs": "^2.6.2",
"#types/node": "^11.9.4",
"ejs": "^2.6.1",
"typescript": "^3.3.3333"
}
}
EJS render HTML
The render script can be run in a terminal window by typing the command npm run render. Make sure to run this command from the directory where your package.json is located. After running the render script you should now see a folder named dist containing a file named index.html.
The contents of index.html should look like this:
Sample Page
Notice that the conditional block containing the model content, in the index.ejs template, is not included in the html output. This is because in the render script the model was passed in as false. Now we'll create an object to pass in as the model with some sample content to the sample page.
In the render.ts file previously created, after the import statements, create an object and add a property to it called content with the value set to a sample of content.
const pageModel = {
content: "This is some sample content. Located on the sample page.",
};
Then pass this object in to the ejs.renderFile function instead of false. The render.ts file should look like this:
//imports
import util = require("util");
import fs = require("fs");
import ejs = require("ejs");
//promisify
const mkdir = util.promisify(fs.mkdir);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const pageModel = {
content: "<p>This is some sample content. Located on the sample page.</p>",
};
async function render() {
try {
//create output directory
await mkdir("dist", { recursive: true });
//render ejs template to html string
//pass pageModel in to render content
const html = await ejs
.renderFile("index.ejs", { model: pageModel })
.then((output) => output);
//create file and write html
await writeFile("dist/index.html", html, "utf8");
} catch (error) {
console.log(error);
}
}
render();
With the model object passed into the template we should now see the conditional block rendered in the index.html output file. Run the command npm run render once more.
The index.html file in the dist folder should now look like this:
<h1>Sample Page</h1>
<p>This is some sample content. Located on the sample page.</p>
The index.ejs template can now render dynamic HTML content according to the model object configured in the render.ts file and by running npm run render after each change to generate an updated index.html file.
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;
In my angular app, I require to import the assets according to the env config. for that, I am trying to download the patch from environment.ts to my assets json file. but have no clue, is there a way to do this? if we can't import directly what is the correct way to do this?
here is my try:
assets/db.json =>
{
"url":"some url"
}
environment.ts:
export const environment = {
"url":"env.url"
}
Live Demo
Angular has an option as resolveJsonModule which is setting at (tsconfig || tsconfig.app).json
To do it:
tsconfig.json
"compilerOptions": {
...
"resolveJsonModule": true,
...
}
environment.ts
import * as db from "<pathofJson>/<fileName>.json"
export const environment = {
url: db.url
}
I have a JSON file that looks like following:
{
"primaryBright": "#2DC6FB",
"primaryMain": "#05B4F0",
"primaryDarker": "#04A1D7",
"primaryDarkest": "#048FBE",
"secondaryBright": "#4CD2C0",
"secondaryMain": "#00BFA5",
"secondaryDarker": "#009884",
"secondaryDarkest": "#007F6E",
"tertiaryMain": "#FA555A",
"tertiaryDarker": "#F93C42",
"tertiaryDarkest": "#F9232A",
"darkGrey": "#333333",
"lightGrey": "#777777"
}
I'm trying to import it into a .tsx file. For this I added this to the type definition:
declare module "*.json" {
const value: any;
export default value;
}
And I'm importing it like this.
import colors = require('../colors.json')
And in the file, I use the color primaryMain as colors.primaryMain. However I get an error:
Property 'primaryMain' does not exist on type 'typeof "*.json"
With TypeScript 2.9.+ you can simply import JSON files with benefits like typesafety and intellisense by doing this:
import colorsJson from '../colors.json'; // This import style requires "esModuleInterop", see "side notes"
console.log(colorsJson.primaryBright);
Make sure to add these settings in the compilerOptions section of your tsconfig.json (documentation):
"resolveJsonModule": true,
"esModuleInterop": true,
Side notes:
Typescript 2.9.0 has a bug with this JSON feature, it was fixed with 2.9.2
The esModuleInterop is only necessary for the default import of the colorsJson. If you leave it set to false then you have to import it with import * as colorsJson from '../colors.json'
The import form and the module declaration need to agree about the shape of the module, about what it exports.
When you write (a suboptimal practice for importing JSON since TypeScript 2.9 when targeting compatible module formatssee note)
declare module "*.json" {
const value: any;
export default value;
}
You are stating that all modules that have a specifier ending in .json have a single export named default.
There are several ways you can correctly consume such a module including
import a from "a.json";
a.primaryMain
and
import * as a from "a.json";
a.default.primaryMain
and
import {default as a} from "a.json";
a.primaryMain
and
import a = require("a.json");
a.default.primaryMain
The first form is the best and the syntactic sugar it leverages is the very reason JavaScript has default exports.
However I mentioned the other forms to give you a hint about what's going wrong. Pay special attention to the last one. require gives you an object representing the module itself and not its exported bindings.
So why the error? Because you wrote
import a = require("a.json");
a.primaryMain
And yet there is no export named primaryMain declared by your "*.json".
All of this assumes that your module loader is providing the JSON as the default export as suggested by your original declaration.
Note: Since TypeScript 2.9, you can use the --resolveJsonModule compiler flag to have TypeScript analyze imported .json files and provide correct information regarding their shape obviating the need for a wildcard module declaration and validating the presence of the file. This is not supported for certain target module formats.
Here's how to import a json file at runtime
import fs from 'fs'
var dataArray = JSON.parse(fs.readFileSync('data.json', 'utf-8'))
This way you avoid issues with tsc slowing down or running out of memory when importing large files, which can happen when using resolveJsonModule.
It's easy to use typescript version 2.9+. So you can easily import JSON files as #kentor decribed.
But if you need to use older versions:
You can access JSON files in more TypeScript way. First, make sure your new typings.d.ts location is the same as with the include property in your tsconfig.json file.
If you don't have an include property in your tsconfig.json file. Then your folder structure should be like that:
- app.ts
+ node_modules/
- package.json
- tsconfig.json
- typings.d.ts
But if you have an include property in your tsconfig.json:
{
"compilerOptions": {
},
"exclude" : [
"node_modules",
"**/*spec.ts"
], "include" : [
"src/**/*"
]
}
Then your typings.d.ts should be in the src directory as described in include property
+ node_modules/
- package.json
- tsconfig.json
- src/
- app.ts
- typings.d.ts
As In many of the response, You can define a global declaration for all your JSON files.
declare module '*.json' {
const value: any;
export default value;
}
but I prefer a more typed version of this. For instance, let's say you have configuration file config.json like that:
{
"address": "127.0.0.1",
"port" : 8080
}
Then we can declare a specific type for it:
declare module 'config.json' {
export const address: string;
export const port: number;
}
It's easy to import in your typescript files:
import * as Config from 'config.json';
export class SomeClass {
public someMethod: void {
console.log(Config.address);
console.log(Config.port);
}
}
But in compilation phase, you should copy JSON files to your dist folder manually. I just add a script property to my package.json configuration:
{
"name" : "some project",
"scripts": {
"build": "rm -rf dist && tsc && cp src/config.json dist/"
}
}
In my case I needed to change tsconfig.node.json:
{
"compilerOptions": {
...
"resolveJsonModule": true
},
"include": [..., "colors.json"]
}
And to import like that:
import * as colors from './colors.json'
Or like that:
import colors from './colors.json'
with "esModuleInterop": true
You should add
"resolveJsonModule": true
as part of compilerOptions to tsconfig.json.
Often in Node.js applications a .json is needed. With TypeScript 2.9, --resolveJsonModule allows for importing, extracting types from and generating .json files.
Example #
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"resolveJsonModule": true,
"esModuleInterop": true
}
}
// .ts
import settings from "./settings.json";
settings.debug === true; // OK
settings.dry === 2; // Error: Operator '===' cannot be applied boolean and number
// settings.json
{
"repo": "TypeScript",
"dry": false,
"debug": false
}
by: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html
Another way to go
const data: {[key: string]: any} = require('./data.json');
This was you still can define json type is you want and don't have to use wildcard.
For example, custom type json.
interface User {
firstName: string;
lastName: string;
birthday: Date;
}
const user: User = require('./user.json');
In an Angular (typescript) app, I needed to include a .json file in my environment.ts. To do so, I had to set two options in tsconfig:
{
"compilerOptions": {
"moduleResolution": "node",
"resolveJsonModule": true
}
}
Then, I could import my json file into the environment.ts:
import { default as someObjectName } from "../some-json-file.json";
You can import a JSON file without modifying tsconfig you tell explicitly that you are importing JSON
import mydata from './mydataonfile.json' assert { type: "json" };
I know this does not fully answer the question but many people come here to know how to load JSON directly from a file.
Enable "resolveJsonModule": true in tsconfig.json file and implement as below code, it's work for me:
const config = require('./config.json');
Note that if you using #kentor ways
Make sure to add these settings in the compilerOptions section of your tsconfig.json (documentation):
You need to add --resolveJsonModule and--esModuleInterop behind tsc command to compile your TypeScript file.
Example:
tsc --resolveJsonModule --esModuleInterop main.ts
require is a common way to load a JSON file in Node.js
in my case I had to change: "include": ["src"] to "include": ["."] in addition to "resolveJsonModule":true because I tried to import manifest.json from the root of the project and not from ./src
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"]
},
...
]
}
...