I want to include my local CSS/JavaScript file in res.response () of hapiJS. I am not able to load my local file. How can I load my local file in the response of Hapi? How to specify directory name?
module.exports = function (req, res) {
Router.run(routes, req.url.path , function (Handler) {
var content = React.renderToString(React.createElement(Handler, { state: '' }));
var head = <link href="/css/app.caz.css" rel="stylesheet"/></script> <script src="js/app.caz.js"></script>;
var page = ` <!DOCTYPE html> <html> ${head} <body> </body> </html>`;
res.response(page);
});
};
Related
Long story short I am working on a single page application that sends commands over a local network. Testing out Electron JS and I can't even seem to get a simple button to work. I feel like I am not linking the logic between main.js and index.js somehow but for the life of me I cannot figure out the correct way to do it. I have even put breakpoints in index.js and through main.js & index.html but none of the breakpoints are hit aside from the ones in main.js. I put a simple function in a preload.js file and that function is correctly called but the one I am trying to attach to a button located in index.html and index.js is never even being hit. A lot of the commented out code is things I want to remember or things I have noticed a different method of creating and just wanted to try and see if that worked. If anyone has any answers or guidance it would be greatly appreciated! :D
Below is my main.js
//#region ---for dev only | hot reload
try {
require('electron-reloader')(module)
} catch (_) {}
//#endregion
const electron = require('electron');
const {app, BrowserWindow, Menu} = require('electron');
const path = require('path');
const ipcMain = electron.ipcMain;
//#region globals
const SRC_DIR = '/src/'
const IMG_DIR = '/assets/images'
//#endregion
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
//frame: false,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
//Used to auto open dev tools for debugging
//win.openDevTools();
win.loadFile('src/index.html');
// win.loadURL(url.format({
// pathname: path.join(__dirname, 'index.html'),
// protocol: 'file',
// slashes: true
// }));
}
app.whenReady().then(() => {
//nativeTheme.shouldUseDarkColors = true;
createWindow();
})
//closes app processes when window is closed
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit();
})
var menu = Menu.buildFromTemplate([
{
label: 'Menu',
submenu: [
{label: 'Edit'},
{type: 'separator'},
{
label: 'Exit',
click() {
app.quit();
}
}
]
}
])
Menu.setApplicationMenu(menu);
Here is index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Ecas Software</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<p id="myText">Let's get started :)</p>
<button id="myBtn">Change Text</button>
<script type="text/javascript" src="./index.js" ></script>
</body>
</html>
Lastly here is my index.js (aka my first and only renderer?)
const electron = require('electron');
const chgBtn = document.getElementById('myBtn');
function replaceText(selector, text){
const element = document.getElementById(selector);
if (element) element.innerText = text;
}
chgBtn.onclick = function() {
replaceText('myText', 'no boom...');
}
// chgBtn.addEventListener('click', function(){
// // if (document.getElementById('myText').innerText == 'boom'){
// // replaceText('myText','no boom...');
// // } else {
// // replaceText('myText','boom');
// // }
// document.alert("working function");
// });
//chgBtn.addEventListener('click', replaceText('myText','no boom...'));
Why you have this error
The problem here is that you didn't use your scripts files the way Electron was intended.
If you use the Devtools Console (by uncommenting win.openDevTools()), you should see this error in your console :
Uncaught ReferenceError: require is not defined (from index.js file)
This is because your index.js file is loaded as a "normal javascript file". If you want to use the Node syntaxe (aka the "require" syntaxe), you need to do it in your preload script. Only the preload script can use the require syntaxe, since it is the only script allowed by Electron to use Node.
You can also use other javascripts files, by import it in your HTML as you did for the index.js file, but you should remove the require call. As the "require" call (on the first line) will throw and error, all the following code will not run. This is why your button did not react on click.
The correct way to do it
If you need to use some methods from the Electron Renderer API (such as the ipcRenderer), you need to put it in your preload script.
If you want to use your own script, in a separate file, you can also do it, you will not be able to directly call Electron API. There is a solution if you want to call the Electron API in your own script, it is called the Context Bridge. This allows you to create an object in your preload script, that can use the Electron API. You can give this object a name, and then call it from your others script by using the window global object.
For example, if you want to use ipcRenderer.send(channel, payload) :
// Preload script
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('theNameYouWant',
{
send: (channel, payload) => ipcRenderer.send(channel, payload)
}
)
// index.js file, imported in your HTML file
window.theNameYouWant.send("channel-name", { someData: "Hello" })
In your example
// Add this in your main.js file to see when a user click on the button from main process
ipcMain.on("button-clicked", (event, data) => console.log(data))
// Preload script
const { contextBridge, ipcRenderer } = require("electron")
contextBridge.exposeInMainWorld("electron", {
send: (channel, payload) => ipcRenderer.send(channel, payload),
})
// index.js
const chgBtn = document.getElementById("myBtn")
function replaceText(selector, text) {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
chgBtn.onclick = function () {
replaceText("myText", "no boom...")
window.electron.send("button-clicked", { someData: "Hello" })
}
I've this html page with a module ...
<html>
<body>
<hello-world color="blue" />
<hello-world color="red" />
<hello-world />
<script type="module">
import { HelloWorld } from './HelloWorld.js'
window.customElements.define('hello-world', HelloWorld)
</script>
</body>
</html>
... and the module content is ...
export class HelloWorld extends HTMLElement {
get color () {
return this.getAttribute('color') || 'gray'
}
set color (value) {
this.setAttribute('color', value)
}
connectedCallback() {
window.requestAnimationFrame(() => {
const div = document.createElement('div')
div.textContent = 'Hello World!!!'
div.style.color = this.color
this.appendChild(div)
});
}
}
I run a PHP server using php -S localhost:8888 -t . and all works fine:
Instead, ... if I move module in ./main.mjs file with content ...
import { HelloWorld } from './HelloWorld.js'
window.customElements.define('hello-world', HelloWorld)
... changing the html part in ...
<html>
<body>
<hello-world color="blue" />
<hello-world color="red" />
<hello-world />
<script type="module" src="main.mjs"></script>
</body>
</html>
... I get the following error:
Failed to load module script: The server responded with a non-JavaScript MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.
Why? May I fix it? How?
You may send the proper content-type (text/javascript) to specify you are sending js files.
exemple with a basic server in node:
const http = require('http');
const fs = require('fs')
const requestListener = function (req, res) {
f = req.url === '/' ? 'index.html' : 'main.mjs'
// commenting that block will give an empty mime type and module will not be loaded
if (f === 'main.mjs') {
res.setHeader('Content-type', 'text/javascript')
}
res.writeHead(200)
return fs.createReadStream(f).pipe(res)
}
const server = http.createServer(requestListener);
server.listen(8080);
console.log('listening: http://localhost:8080')
index.html
<!DOCTYPE html>
<html>
<body>
<div id="junk">loading failed</div>
<script type="module" src="main.mjs"></script>
</body>
</html>
main.js
document.getElementById('junk').innerHTML = 'loading ok'
see Troubleshooting
PHP's simple built-in server doesn't have a MIME type registered for .mjs files. It's not a common extension and is typically only used for ES6 modules in Node.js (and isn't needed there any more thanks to the type option in package.json).
Rename the file to have a .js extension again and your HTTP server will send the correct Content-Type for it.
I'm trying to get text file data located in the same directory where my .vue file is. But it's not returning the text on both chrome and firefox. Instead it's returning following response, which is not the content of my text file.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>router-project</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<script type="text/javascript" src="/app.js"></script></body>
</html>
Following is my vue file.
<template>
<body>
<div> hello world </div>
</body>
</template>
<script>
var $ = require('jquery');
window.jQuery = $;
export default {
data () {
return {
}
},
created () {
this.getPoemList(),
},
methods: {
getPoemList () {
function reqListener () {
console.log(this.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "hello.txt");
oReq.send();
} // getPoemList function ends
} // methods end
} // export default ends
</script>
<style scoped>
</style>
Contents of hello.txt are following.
hello
I assume you're using Webpack, since you have a .vue file (requiring the vue-loader Webpack plugin)...
You can use raw-loader to load the .txt file as a string.
Install raw-loader from NPM with:
npm i -D raw-loader
In <projectroot>/vue.config.js, configure Webpack to use raw-loader for *.txt:
module.exports = {
//...
chainWebpack: config => {
config.module
.rule('raw')
.test(/\.txt$/)
.use('raw-loader')
.loader('raw-loader')
.end()
},
}
In your component's .vue file, use import or require to load hello.txt:
<script>
import helloText from './hello.txt'; // OR: const helloText = require('./hello.txt')
export default {
//...
methods: {
getPoemList () {
console.log({ helloText });
}
}
}
</script>
<template>
<body>
<div> hello world {{variable}}</div>
</body>
</template>
<script>
var $ = require('jquery');
window.jQuery = $;
export default {
data() {
return {
variable: "",
}
},
mounted() {
methods: {
// create a vm variable pointing this
const vm = this;
function reqListener() {
// captures the local value this.responseText to vm (this vuejs) vm.variable
vm.variable = this.responseText;
console.log(this.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "hello.txt");
oReq.send();
}
}
}
</script>
<style> </style>
I am unable to use JSDOM (version 13.0.0) to load scripts from the local filesystem with a relative path.
I have taken a look at the following questions but they do not answer my question:
jsdom can't load local html and javascript (I have already followed the runScripts and resources suggestion there).
File foo.js:
var jsdom = require('jsdom')
var html = `<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script src="bar.js"></script>
</head>
<body>
<div>Test</div>
</body>
</html>`
global.window = new jsdom.JSDOM(html, { runScripts: "dangerously", resources: "usable" }).window
console.log('foo')
File bar.js:
console.log('bar')
Here is the error I get:
$ node foo.js
foo
Error: Could not load script: "bar.js"
at onErrorWrapped (/Users/lone/so/node_modules/jsdom/lib/jsdom/browser/resources/per-document-resource-loader.js:41:19)
at Object.check (/Users/lone/so/node_modules/jsdom/lib/jsdom/browser/resources/resource-queue.js:72:23)
at request.then.catch.err (/Users/lone/so/node_modules/jsdom/lib/jsdom/browser/resources/resource-queue.js:124:14)
at process._tickCallback (internal/process/next_tick.js:68:7)
at Function.Module.runMain (internal/modules/cjs/loader.js:746:11)
at startup (internal/bootstrap/node.js:240:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:564:3) Error: Tried to fetch invalid URL bar.js
at ResourceLoader.fetch (/Users/lone/so/node_modules/jsdom/lib/jsdom/browser/resources/resource-loader.js:84:29)
at PerDocumentResourceLoader.fetch (/Users/lone/so/node_modules/jsdom/lib/jsdom/browser/resources/per-document-resource-loader.js:16:42)
at HTMLScriptElementImpl._fetchExternalScript (/Users/lone/so/node_modules/jsdom/lib/jsdom/living/nodes/HTMLScriptElement-impl.js:92:30)
at HTMLScriptElementImpl._eval (/Users/lone/so/node_modules/jsdom/lib/jsdom/living/nodes/HTMLScriptElement-impl.js:161:12)
at HTMLScriptElementImpl._poppedOffStackOfOpenElements (/Users/lone/so/node_modules/jsdom/lib/jsdom/living/nodes/HTMLScriptElement-impl.js:126:10)
at OpenElementStack.pop (/Users/lone/so/node_modules/jsdom/lib/jsdom/browser/htmltodom.js:17:12)
at Object.endTagInText [as END_TAG_TOKEN] (/Users/lone/so/node_modules/parse5/lib/parser/index.js:2153:20)
at Parser._processToken (/Users/lone/so/node_modules/parse5/lib/parser/index.js:657:55)
at Parser._processInputToken (/Users/lone/so/node_modules/parse5/lib/parser/index.js:684:18)
at Parser._runParsingLoop (/Users/lone/so/node_modules/parse5/lib/parser/index.js:440:18)
How can I load a local JavaScript file while using JSDOM?
JSDOM doesn't know where to look for that file locally while executing. So running your example you can follow any of this two approaches.
1st Approach
You have to wait for the script file to load and execute.
Create a three files index.html,index.js and test.js into the same folder.
index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
abc
<script src='index.js'></script>
</body>
</html>
index.js
document.body.textContent = 123;
test.js
'use strict';
const { JSDOM } = require('jsdom');
const options = {
resources: 'usable',
runScripts: 'dangerously',
};
JSDOM.fromFile('index.html', options).then((dom) => {
console.log(dom.window.document.body.textContent.trim());
setTimeout(() => {
console.log(dom.window.document.body.textContent.trim());
}, 5000);
});
// console output
// abc
// 123
2nd Approach
Set the external scripts base root folder in JSDOM env.
js/index.js
console.log('load from jsdom');
var loadFromJSDOM = 'load from jsdom';
test.js
'use strict';
const { JSDOM } = require('jsdom');
JSDOM.env({
html: "<html><body></body></html>",
documentRoot: __dirname + '/js',
scripts: [
'index.js'
]
}, function (err, window) {
console.log(window.loadFromJSDOM);
}
);
Read more from these references
https://github.com/jsdom/jsdom/issues/1867
jsdom.env: local jquery script doesn't work
Great answer from front_end_dev. It helped me a lot and I will share how my code works with this solution to be more clear. Maybe will help others.
import "#testing-library/jest-dom";
import { logDOM } from "#testing-library/dom";
import { JSDOM } from "jsdom";
import fs from "fs";
import path from "path";
const html = fs.readFileSync(path.resolve(__dirname, "../index.html"), "utf8");
let dom;
let container;
jest.dontMock("fs");
function waitForDom() {
return new Promise((resolve) => {
dom = new JSDOM(html, {
runScripts: "dangerously",
resources: "usable",
url: `file://${path.resolve(__dirname, "..")}/index.html`,
});
dom.window.document.addEventListener("DOMContentLoaded", () => {
resolve();
});
});
}
beforeAll(() => waitForDom());
beforeEach(() => {
container = dom.window.document.body;
});
afterEach(() => container = null)
it("should ", () => {
logDOM(container);
});
Based on the tutorials I tried to set up a basic server in NodeJS via HapiJS.
The initialization looks like this:
//globals
mainAddr = "MoriMachine";
mainPort = 3000;
require('./backend/app.js')
This is the content of app.js:
const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({host: mainAddr, port: mainPort });
server.register(require('inert'), (err) => {
if (err) { throw err; }
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
reply.file('./public/index/index.html');
}
});
});
server.start((err) => {
if (err) { throw err; }
console.log(`Server running at: ${server.info.uri}`);
});
While index.html is similarly small:
<!DOCTYPE html>
<html>
<head>
<script src="index.js"></script>
</head>
<body>
<h1>HELLO WORLD!! -3-</h1>
<img src="test.jpg"></img>
</body>
</html>
In the project itself, there are init.js and three folders:
node_modules: for obvious reasons
backend: contains app.js
public: contains folder 'index' that contains the HTML, and the JS and the picture I tried to attach.
The problem is that whatever path I try, when I run the server, neither the JS or the picture are found.
What is the reason? An I missing to add some additional functionality?
The reason is that you only got one route that serves one specific file. When you access the route '/' your browser tries to access '/index.js' and '/test.jpg' and you are not responding to these routes.
One way to do is is to serve everything in your public directory, like this:
server.route({
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: 'public'
}
}
});
Now your index.js is accessible trough /index/index.js and your image is accessible trough /index/test.jpg
so make these changes to your html
<!DOCTYPE html>
<html>
<head>
<script src="/index/index.js"></script>
</head>
<body>
<h1>HELLO WORLD!! -3-</h1>
<img src="/index/test.jpg"></img>
</body>
</html>
Note that in this way your index.html is also accessible trough /index/index.html
for more detaisl look at: http://hapijs.com/tutorials/serving-files?lang=en_US#directory-handler