Webpack Externals , load a local file on runtime - javascript

I have a file uiConfig.json, it has some server URL, and the DevOps team changes the files based on the environment.
`
I am trying to load this file on runtime using webpack.
So far I have tried the following.
Using Webpack Externals
//webpack.config.js
externals: {
'uiConfig': JSON.stringify(require('./uiConfig.json'))
},
copied uiConfig at root path of build directory
//app.js
import uiConfig from 'uiConfig'
Fetching the uiConfig from the script tag and exposing the url to global window object.
This method seems to be working sometimes, but its not consistent
<script type="application/javascript">
window.uiConfig = {}
fetch('./uiConfig.json').then((response) => {
return response.json();
}).then((data) => {
window.uiConfig = data
})
</script>
What am I doing wrong, how could I tackle this problem?
Appreciate your help.

Related

Environment variables not working (Next.JS 9.4.4)

So i'm using the Contentful API to get some content from my account and display it in my Next.Js app (i'm using next 9.4.4). Very basic here. Now to protect my credentials, i'd like to use environment variables (i've never used it before and i'm new to all of this so i'm a little bit losted).
I'm using the following to create the Contentful Client in my index.js file :
const client = require('contentful').createClient({
space: 'MYSPACEID',
accessToken: 'MYACCESSTOKEN',
});
MYSPACEID and MYACCESSTOKEN are hardcoded, so i'd like to put them in an .env file to protect it and don't make it public when deploying on Vercel.
I've created a .env file and filled it like this :
CONTENTFUL_SPACE_ID=MYSPACEID
CONTENTFUL_ACCESS_TOKEN=MYACCESSTOKEN
Of course, MYACCESSTOKEN and MYSPACEID contains the right keys.
Then in my index.js file, i do the following :
const client = require('contentful').createClient({
space: `${process.env.CONTENTFUL_SPACE_ID}`,
accessToken: `${process.env.CONTENTFUL_ACCESS_TOKEN}`,
});
But it doesn't work when i use yarn dev, i get the following console error :
{
sys: { type: 'Error', id: 'NotFound' },
message: 'The resource could not be found.',
requestId: 'c7340a45-a1ef-4171-93de-c606672b65c3'
}
Here is my Homepage and how i retrieve the content from Contentful and pass them as props to my components :
const client = require('contentful').createClient({
space: 'MYSPACEID',
accessToken: 'MYACCESSTOKEN',
});
function Home(props) {
return (
<div>
<Head>
<title>My Page</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main id="page-home">
<Modal />
<NavTwo />
<Hero item={props.myEntries[0]} />
<Footer />
</main>
</div>
);
}
Home.getInitialProps = async () => {
const myEntries = await client.getEntries({
content_type: 'mycontenttype',
});
return {
myEntries: myEntries.items
};
};
export default Home;
Where do you think my error comes from?
Researching about my issue, i've also tried to understand how api works in next.js as i've read it could be better to create api requests in pages/api/ but i don't understand how to get the content and then pass the response into my pages components like i did here..
Any help would be much appreciated!
EDIT :
So i've fixed this by adding my env variables to my next.config.js like so :
const withSass = require('#zeit/next-sass');
module.exports = withSass({
webpack(config, options) {
const rules = [
{
test: /\.scss$/,
use: [{ loader: 'sass-loader' }],
},
];
return {
...config,
module: { ...config.module, rules: [...config.module.rules, ...rules] },
};
},
env: {
CONTENTFUL_SPACE_ID: process.env.CONTENTFUL_SPACE_ID,
CONTENTFUL_ACCESS_TOKEN: process.env.CONTENTFUL_ACCESS_TOKEN,
},
});
if you are using latest version of nextJs ( above 9 )
then follow these steps :
Create a .env.local file in the root of the project.
Add the prefix NEXT_PUBLIC_ to all of your environment variables.
eg: NEXT_PUBLIC_SOMETHING=12345
use them in any JS file like with prefix process.env
eg: process.env.NEXT_PUBLIC_SOMETHING
You can't make this kind of request from the client-side without exposing your API credentials. You have to have a backend.
You can use Next.js /pages/api to make a request to Contentful and then pass it to your front-end.
Just create a .env file, add variables and reference it in your API route as following:
process.env.CONTENTFUL_SPACE_ID
Since Next.js 9.4 you don't need next.config.js for that.
By adding the variables to next.config.js you've exposed the secrets to client-side. Anyone can see these secrets.
New Environment Variables Support
Create a Next.js App with Contentful and Deploy It with Vercel
Blog example using Next.js and Contentful
I recomended to update at nextjs 9.4 and up, use this example:
.env.local
NEXT_PUBLIC_SECRET_KEY=i7z7GeS38r10orTRr1i
and in any part of your code you could use:
.js
const SECRET_KEY = process.env.NEXT_PUBLIC_SECRET_KEY
note that it must be the same name of the key "NEXT_PUBLIC_ SECRET_KEY" and not only "SECRET_KEY"
and when you run it make sure that in the log says
$ next dev
Loaded env from E:\awesome-project\client\.env.local
ready - started server on http://localhost:3000
...
To read more about environment variables see this link
Don't put sensitive things in next.config.js however in my case I have some env variables that aren't sensitive at all and I need them Server Side as well as Client side and then you can do:
// .env file:
VARIABLE_X=XYZ
// next.config.js
module.exports = {
env: {
VARIABLE_X: process.env.VARIABLE_X,
},
}
You have to make a simple change in next.config.js
const nextConfig = {
reactStrictMode: true,
env:{
MYACCESSTOKEN : process.env.MYACCESSTOKEN,
MYSPACEID: process.env.MYSPACEID,
}
}
module.exports = nextConfig
change it like this
Refer docs
You need to add a next.config.js file in your project. Define env variables in that file and those will be available inside your app.
npm i --save dotenv-webpack#2.0.0 // version 3.0.0 has a bug
create .env.development.local file in the root. and add your environment variables here:
AUTH0_COOKIE_SECRET=eirhg32urrroeroro9344u9832789327432894###
NODE_ENV=development
AUTH0_NAMESPACE=https:ilmrerino.auth0.com
create next.config.js in the root of your app.
const Dotenv = require("dotenv-webpack");
module.exports = {
webpack: (config) => {
config.resolve.alias["#"] = path.resolve(__dirname);
config.plugins.push(new Dotenv({ silent: true }));
return config;
},
};
However those env variables are gonna be accessed by the server. if you want to use any of the env variables you have to add one more configuration.
module.exports = {
webpack: (config) => {
config.resolve.alias["#"] = path.resolve(__dirname);
config.plugins.push(new Dotenv({ silent: true }));
return config;
},
env: {
AUTH0_NAMESPACE: process.env.AUTH0_NAMESPACE,
},
};
For me, the solution was simply restarting the local server :)
Gave me a headache and then fixed it on accident.
It did not occur to me that env variables are loaded when the server is starting.

Keep a ES module outside the bundle

My application has a configuration file
external-config.js
import {constants} from "./constants.js";
export const ExternalConfig = {
title: "My application",
version: "2.0",
constants: constants
list: ["uno", "due", "tre"]
}
I don't want it to be bundled with the application, I want to keep it outside. I tried the IgnorePlugin, but apparently this simply breaks the dependency graph and I get the error ReferenceError: Config is not defined even if the reference to the path of the config file in the budle is correct.
plugins: [
new webpack.IgnorePlugin({
checkResource (resource) {
if (resource === "./conf/external-config.js") return true;
return false;
}
})
]
I cannot even import it in the main html page like
<script type="module" src="./conf/config.js"></script>
because in this way I couldn't access the object outside its own script.
Is there a way to do that?
EDIT: following #raz-nonen advice, I tried null-loader, it seems it could be a solution. The problem with that is the physical position of the configuration file.
rules: [
{
test: path.resolve(__dirname, "src/conf/external-config.js"),
use: "null-loader"
}
...]
And this is the result in the built script
// EXTERNAL MODULE: ./src/conf/external-config.js
var external_config = __webpack_require__(13);
But the actual position of the configuration in the dist folder is ./conf/external-config.js, not ./src/conf/external-config.js
This is the chunk of my app that consumes the external file
import {ExternalConfig} from "./conf/external-config.js";
class MyApp extends LitElement {
constructor() {
super();
console.log(ExternalConfig.list)
}
}
You'll need to make this file available in the dist folder. You can do that with copy-webpack-plugin
Tell webpack that ExternalConfig will be imported from external. It means that you'll have to take care that it'll be available at runtime. You can do it simply by importing your conf/config.js that you copied from a <script> tag in your index.html.
Add:
externals: {
'conf/external-config': 'conf/external-config'
}
In your webpack configuration.

reactJS and reading a text file

I have a reactJS application where I need to read in a large text file and use the data from the file to populate some arrays. I came across some code examples and I am trying to implement this code:
readTextFile = file => {
var rawFile = new XMLHttpRequest();
rawFile.open("GET", file, false);
rawFile.onreadystatechange = () => {
if (rawFile.readyState === 4) {
if (rawFile.status === 200 || rawFile.status == 0) {
var allText = rawFile.responseText;
console.log("allText: ", allText);
this.setState({
fundData: allText
});
}
}
};
rawFile.send(null);
};
The code is called by executing this line of code:
this.readTextFile("../text/fund_list.txt");
When I execute the application, I get the following error message:
GET http://localhost:8080/text/fund_list.txt 404 (Not Found)
This is what my application structure looks like:
The readTextFile function is in the account_balanc.js code and I am trying to read in the text file /text/fund_list.txt. The file does exist so obviously I am not referencing the file correctly but I don't know why.
I have tried this.readTextFile("../text/fund_list.txt") and this.readTextFile("./text/fund_list.txt"); but neither worked. I even tried moving fund_list.txt into the /screens folder and changing the function call to this.readTextFile("fund_list.txt"); but I still get the 404 error message.
Any ideas why?
Thank you.
I moved the fund_list.txt file into the public/text folder (after I created it) and changed the calling of the function to this.readTextFile("./text/fund_list.txt");
I saw this as a posted solution but then the post was removed (I don't know why) so I can't thank the user who posted it. But this solved my problem.
You can use webpack raw loader and directly import the .txt file into the component.
First, install raw-loader:
npm i -D raw-loader
Second, add the loader to your webpack config:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: 'raw-loader'
}
]
}
}
Then, directly import .txt to your component:
import txt from './file.txt';
I'm assuming you are using Webpack as the bundler.
The reason you were getting a 404 Not Found is because you need to configure Webpack to bundle the file into the build folder the website is hosting.
Moving it to the /public folder worked because Webpack was configured to bundle all the files in the /public folder as it is usually the entry point of the project (since this is probably create-react-app).
Also the folks at create-react-app are smart and saw that if it was a .txt extension file, they would bundle them into a /text folder in the build file, just as any .jpg or .png files will bundle into a /images folder.
Note that a lot of files need a special webpack loader if you import them in project, but you can also use a webpack plugin to copy the file into your bundle and then read that programmatically as you are doing.
Therefore once you moved it to that folder your application could actually find it in the build folder -hosted in localhost:8080 !
Using HTML5 FileReader API you can easily read any file. Please have look at the following code:
function App() {
const [json, setJson] = useState("");
let fileInputRef = React.createRef();
return (
<div className="App">
<p>{json}</p>
<input
type="file"
ref={fileInputRef}
style={{ display: "none" }}
onChange={async e => {
const reader = new FileReader();
reader.onload = function() {
const text = reader.result;
setJson(text);
};
reader.readAsText(e.target.files[0]);
}}
accept=".json,application/json"
/>
<button
onClick={() => {
fileInputRef.current.click();
}}
>
Upload JSON file
</button>
</div>
);
}
Here is a working Demo
I don't have a lot of experience with pure javascript applications, I usually use a rails backend, but I would recommend focusing on just being able to hit http://localhost:8080/text/fund_list.txt in a browser and get a response. Once you solve that it looks like the javascript will do it's thing.
Try...
const file = require("../text/fund_list.txt");
Then
this.readTextFile(file);

Change configuration values post webpack build

In a static folder I have config.js
module.exports = {
config: {
urls: {
auth: localhost
}
}
}
I run npm run build and send the output (dist folder) to the client to deploy in their production environment. I want the client to be able to edit the value of auth.
config is currently configured as a external file in webpack:
const config = require(path.join(paths.STATIC, 'config.js'))
externals: [{
appsetting: JSON.stringify(config)
}]
How do I make config.js recognize changes post webpack build?
How about something like this, using axios:
function readConfig () {
return axios.get('./static/config.js').then((response) => {
return response.data
});
}
readConfig().then((config) => {
// Do stuff
});
And make sure config.js is copied to the static/ folder.
Create an entry file in webpack.config for config.js and import/require config.js in other files where you consume the config.

Excluding Webpack externals with library components / fragments

Webpack has been very useful to us in writing isomorphic Javascript, and swapping out npm packages for browser globals when bundling.
So, if I want to use the node-fetch npm package on Node.js but exclude it when bundling and just use the native browser fetch global, I can just mention it in my webpack.config.js:
{
externals: {
'node-fetch': 'fetch',
'urlutils': 'URL',
'webcrypto': 'crypto', // etc
}
}
And then my CommonJS requires const fetch = require('node-fetch') will be transpiled to const fetch = window.fetch (or whatever it does).
So far so good. Here's my question: This is easy enough when requiring entire modules, but what about when I need to require a submodule / individual property of an exported module?
For example, say I want to use the WhatWG URL standard, isomorphically. I could use the urlutils npm module, which module.exports the whole URL class, so my requires look like:
const URL = require('urlutils')
And then I can list urlutils in my externals section, no prob. But the moment I want to use a more recent (and more supported) npm package, say, whatwg-url, I don't know how to Webpack it, since my requires look like:
const { URL } = require('whatwg-url')
// or, if you don't like destructuring assignment
const URL = require('whatwg-url').URL
How do I tell Webpack to replace occurrences of require('whatwg-url').URL with the browser global URL?
At first I would like to highlight that I am not a webpack expert. I think there is a better way of bundling during the build time. Anyway, here is my idea:
webpack.config.js
module.exports = {
target: "web",
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js"
}
};
entry.js
var URL = require("./content.js");
document.write('Check console');
console.log('URL function from content.js', URL);
content.js
let config = require('./webpack.config.js');
let urlutils = require('urlutils');
let whatwgUrl = require('whatwg-url');
console.log('urlutils:', urlutils);
console.log('whatwgUrl', whatwgUrl);
module.exports = {
URL: undefined
};
if (config.target === 'web') {
module.exports.URL = urlutils;
} else {
module.exports.URL = whatwgUrl.URL;
}
index.html
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript" src="bundle.js" charset="utf-8"></script>
</body>
</html>
As I said in the comment, it's going to bundle two libs for the Web bundle - waste of space.
Now, for NodeJS, you change the target from web to node and it should take the other library. https://webpack.github.io/docs/configuration.html#target
I've found a module for 'isomorphic' apps: https://github.com/halt-hammerzeit/universal-webpack
I think you could try to use two, separate middle content.js files as a parameters for the module. One containing urlutis and the second whatwg-url. Then it would dynamically recognize what it compiles your files for and use the proper module.
Hope it helps.

Categories

Resources