Following the API documentation, I don't understand how to define a Content-Security-Policy HTTP Header for the renderer of my Electron application. I always get a warning in the DevTools.
I tried:
1) Copy/Paste the code in the API Doc, blindly:
app.on('ready', () => {
const {session} = require('electron')
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({responseHeaders: `default-src 'self'`})
})
win = new BrowserWindow(...)
win.loadUrl(...)
}
(By the way, I don't get why "Content-Security-Policy:" is missing in the string. But adding it don't change anything)
2) Modifying the session of the renderer with the same code:
win = new BrowserWindow(...)
win.loadUrl(...)
const ses = win.webContents.session;
ses.webRequest.onHeadersReceived((details, callback) => {
callback({responseHeaders: `default-src 'self'`})
})
3) Add an extra header to ther renderer:
win = new BrowserWindow(...)
win.loadURL(`file://${__dirname}/renderer.html`,{
extraHeaders: `Content-Security-Policy: default-src 'self'`
});
...
The only thing that works is using a meta tag in the renderer HTML file:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'>
Not sure why the documentation contains this broken code. It confused the hell out of me but I found a working solution by trial and error:
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({ responseHeaders: Object.assign({
"Content-Security-Policy": [ "default-src 'self'" ]
}, details.responseHeaders)});
});
So the headers argument must be an object with the same structure as the original headers received in details.responseHeaders. And the original headers must be included in the passed object as well because this object seems to completely replace the original response headers.
The extraHeaders option isn't for response headers. It is for request headers sent to the server.
If your aim is to be able to use CSP in both dev mode (with resources loaded by http:// protocol) and prod mode (file:// protocol) here's how you can do it:
First, remove the Content-Security-Policy meta from src/index.html - we need to inject it only for prod mode, because
onHeadersReceived will not work for file:// protocol as Electron docs confirm, and also because
if we keep it in src/index.html for Dev mode it will override the onHeadersReceived at least for part of resources, and for Dev mode we need different settings.
Then we can inject it for Prod mode with gulp-inject:
// in project dir
npm install --save-dev gulp-inject gulp
// src/index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<base href="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- inject:prod-headers -->
<!-- src/prod-headers.html content will be injected here -->
<!-- endinject -->
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
// src/prod-headers.html
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
// gulpfile.js
var gulp = require('gulp');
var inject = require('gulp-inject');
gulp.task('insert-prod-headers', function () {
return gulp.src('./dist/index.html')
.pipe(inject(gulp.src('./src/prod-headers.html'), {
starttag: '<!-- inject:prod-headers -->',
transform: function (filePath, file) {
// return file contents as string
return file.contents.toString('utf8')
}
}))
.pipe(gulp.dest('./dist'));
});
Then make sure npx gulp insert-prod-headers is run after e.g. ng build generates dist/index.html.
And for dev mode let's use onHeadersReceived similarly to Electron docs example:
const args = process.argv.slice(1);
const devMode = args.some((val) => val === '--serve');
app.on('ready', () => {
if (devMode) {
const {session} = require('electron')
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({responseHeaders: `default-src http: ws:`})
})
}
win = new BrowserWindow(...)
win.loadUrl(...)
}
This solution was tested on Electron 4.0.3.
As pointed out in the Electron docs, you will have to use a Content Security Policy (CSP) Meta Tag in the html file when you load your renderer.html via file:// scheme (IIRC you do that in above example).
If you want to adjust the content security policy conditionally for prod and dev environment, you can dynamically generate this string inside the html in your build step. I suggest using a template engine like mustache.js (used in the example).
Example (file resources)
In my case I wanted to enable Hot Module Replacement (HMR) via websockets and file:// resource in dev mode, which required relaxing the CSP rules (but only in dev!).
index.mustache:
<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="{{{cspContent}}}"
/>
</head>
...
cspContent.json for dev:
{
"cspContent": "default-src 'self'; connect-src 'self' ws:"
}
build step in dev (for prod default values could be used):
npx mustache cspContent.json index.mustache > index.html
URL resources
For usage with URL resources, you can stick to this example:
const { session } = require('electron')
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': ['default-src \'none\'']
}
})
})
Make sure to merge your custom CSP response headers with the default ones - you don't do that in your pasted example above. Here, you can also check conditionally for the environment.
Hope, it helps.
There isn't enough detail in your question to know whether you are having issues on initial load or subsequent web requests, but my issue was for the initial file load. With an Electron app using React, I was getting warnings about using inline scripts even with kayahr's code. This is because the onHeadersReceived method only catches requests that are made after the application has loaded initially. It will not stop any warnings from the initial application load.
I ended up having to use templating during my application build to add a nonce to the inline script and style and to the CSP header in the HTML file that the application loads initially.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-<%= scriptNonce %>'; style-src 'nonce-<%= styleNonce %>';">
<link rel="stylesheet" type="text/css" href="./index.css" nonce=<%= styleNonce %>>
<title>Basic Electron App</title>
</head>
<body>
<div id="app"></div>
<script type="application/javascript" nonce=<%= scriptNonce %>>
require('./index.js');
</script>
</body>
</html>
index.css
body {
margin: 0px;
}
.hello {
font-family: "Century Gothic";
width: 800px;
margin: 70px auto;
text-align: center;
}
and in gulfile.js add the following to what you already have and make sure this task is included in your pipeline. You can also just update your current html task with the code below.
const template = require('gulp-template');
const uuidv4 = require('uuid/v4');
gulp.task('copy-html', () => {
// Create nonces during the build and pass them to the template for use with inline scripts and styles
const nonceData = {
scriptNonce: new Buffer(uuidv4()).toString('base64'),
styleNonce: new Buffer(uuidv4()).toString('base64')
};
return gulp.src('src/*.html')
.pipe(template(nonceData))
.pipe(gulp.dest('dist/'));
});
This is a very stripped down example. I have a more complete example at https://github.com/NFabrizio/data-entry-electron-app if anyone is interested, though there is still one warning when running the application because one of the packages I am using pulls in react-beautiful-dnd, which adds inline styles but does not currently accept nonces.
Set the following meta tag in the renderers.
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-xxx or sha256-yyy' " />
Kindly checkout my github repo electron-renderer-CSP-sample, containing samples for both nonce & SHA methods for internal & external js files as well.
Related
In my Rack-based app I want to serve CSS and JS and so I use Rack::Static middleware as shown below:
config.ru
use Rack::Static, urls: ["/css" ], root: "public"
run MyApp
public folder structure:
public
css
application.min.css
As per Rack::Static implementation at https://github.com/rack/rack/blob/2.2.4/lib/rack/static.rb (link refers to code in the version of Rack I am using i.e. 2.2.4) by default Cache-Control header will not be set
in Response.
But if I use following configuration
use Rack::Static, urls: ["/css" ], root: "public",
:header_rules => [
# Cache CSS/JS files, matching given regex in public caches (e.g. Rack::Cache) as well as in the browser. For e.g. myfile.1.2.1.css
#
[ /\.(?:[1-9]\.[0-9]\.[0-9])\.(?:css|js)\z/, {'cache-Control' => 'public, max-age=60'} ]
]
Then I can see following header Cache-Control: public, max-age=60 under Response Headers for e.g. in Network tab under Web Developer Tools in Firefox.
Now I want to cache bust that CSS file using fingerprint strategy as explained in following resources I found
https://css-tricks.com/strategies-for-cache-busting-css/#aa-changing-file-name
https://csswizardry.com/2019/03/cache-control-for-civilians/
So in my HTML pages I would have my stylesheet name include the fingerprint version for e.g. like following
<head>
...
...
<link href="/css/application.min.<MY_ASSET_VERSION>.css" rel="stylesheet">
</head>
where say <MY_ASSET_VERSION> is set to 1.0.0.
But I should not have any file by name application.min.1.0.0.css in my public folder. That naming is just done so as to trigger cache bust. So how can I make Rack::Static
to serve the file css/application.min.css when it encounters path /css/application.min.1.0.0.css?
Will I need to implement a middleware which should be put in application's middleware stack after Rack::Static? If yes, can anybody please help me with an example because I have not implemented any middleware.
Or if there is any other standard way for addressing the need at hand, then please suggest that.
Thanks.
Posting below the solution which I implemented using a middleware and which is working for me.
middlewares/custom_middleware/util.rb
module CustomMiddleware
module Util
extend self
EXTENSIONS_OF_ASSETS_TO_BE_FINGER_PRINTED = /css|js/
ASSET_FINGER_PRINT_FORMAT_REGEX = /[1-9]\.[0-9]\.[0-9]/
FINGER_PRINTED_ASSET_NAME_MATCHER_REGEX = /\.(?:#{ASSET_FINGER_PRINT_FORMAT_REGEX})\.(?:#{EXTENSIONS_OF_ASSETS_TO_BE_FINGER_PRINTED})\z/
ORIGINAL_ASSET_NAME_DETERMINER_FROM_FINGER_PRINTED_NAME_REGEX = /(.+)\.(?:#{ASSET_FINGER_PRINT_FORMAT_REGEX})\.(#{EXTENSIONS_OF_ASSETS_TO_BE_FINGER_PRINTED})\z/
def determine_original_asset_name(fingerprinted_asset_name:)
md = fingerprinted_asset_name.match(ORIGINAL_ASSET_NAME_DETERMINER_FROM_FINGER_PRINTED_NAME_REGEX)
return fingerprinted_asset_name if md.nil?
arr = md.captures
asset_file_name = arr[0]
asset_file_extension = arr[1]
asset_name = "#{asset_file_name}.#{asset_file_extension}"
asset_name
end
end
end
middlewares/custom_middleware/fingerprinted_asset_name_modifier.rb
require_relative 'util'
module CustomMiddleware
class FingeprintedAssetNameModifier
def initialize(app)
#app = app
end
def call(env)
env_path_info_key = 'PATH_INFO'
orig_path = env[env_path_info_key]
modified_path = Util.determine_original_asset_name(fingerprinted_asset_name: orig_path)
if modified_path != orig_path
env.merge!(env_path_info_key => modified_path)
end
#app.call(env)
end
end
end
config.ru
require_relative "middlewares/custom_middleware/fingerprinted_asset_name_modifier"
use CustomMiddleware::FingeprintedAssetNameModifier
use Rack::Static, urls: ["/css", "/js" ], root: "public",
:header_rules => [
# Cache CSS/JS files in public caches (e.g. Rack::Cache) as well as in the browser. For e.g. myfile.css
[ %w(css js), {'cache-control' => 'public, max-age=60'} ]
]
run MyApp
With above solution when following CSS file is included in my page
<head>
...
...
<link href="/css/application.min.1.0.0.css" rel="stylesheet">
</head>
application.min.1.0.0.css file serves my file at public/css/application.min.css and in Response headers Cache-Control: public, max-age=60 is set implying the after 60 seconds if application.min.1.0.0.css is re-requested it will be served from my application and not from browser's cache.
Also within 60 seconds of 1st request to the asset if changing the asset fingerprint in page like following
<link href="/css/application.min.1.0.5.css" rel="stylesheet">
and reloading the page, the asset is served from my application and not from browser's cache.
Hoping that this turns out to be useful to people who may come up with a requirement like in question post.
Thanks.
my problem is - I got a main.js file, and a functions.js file, where I want some of my functions to be. However, whenever I use require in that functions.js file, I get an error, require is not defined.
I've read posts about other people having similar problem, but in their case setting nodeIntegration to true helped. That's what I had from the beginning. I know the problems of this solution, but at the moment I don't need the app to be secure, so I would be fine with this solution, if it worked. It doesn't.
I tried preloading, but I think it's just a "safer" equivalent of nodeIntegration solution. And being an equivalent, it doesn't work either. Can you help me?
My code:
main.js
const { app, BrowserWindow, ipcMain } = require('electron')
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
preload: 'functions.js'
}
})
win.maximize();
win.loadFile('index.html');
}
app.on('ready', function() {
createWindow();
createOpenDialog();
});
functions.js (literally, this is all it takes for the code to fail)
const electron = require('electron');
index.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="main.css">
<meta charset="UTF-8">
<title>Hello World!</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
</head>
<body style="background: white;">
<script src="functions.js">
</script>
</body>
</html>
So, one thing I didn't try was setting contextIsolation to false. And least it seems like it, because I was sure I tried it. Anyway, this seems to fix the issue, although I will need to learn what it does exactly. I can do "require" now, and already tested if it works by using ipcRenderer.
Context isolation is a security feature that exists on Electron. Its whole purpose is to separate the preload scripts and Electrons internal apis from your website so it doesn't have any access it should not have.
From Electron 12 context isolation is off by default .This means that if you need to expose some functionality in your preload scripts you will have to use context bridge
An example from the documentation is this :
Before context isolation was off
window.myAPI = {
doAThing: () => {}
}
After : context isolation is on
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
doAThing: () => {}
})
You can read more about this feature in the docs :
https://www.electronjs.org/docs/tutorial/context-isolation
I wanted to set title to my webpage created with vue cli 3 and thus looked into public/index.html. There, I found <title><%= htmlWebpackPlugin.options.title %></title>.
How do I set and modify htmlWebpackPlugin.options.title in vue cli 3 project?
Looking at the popularity of the question, I decided to add an elaborate answer with references to make it more authentic and complete. I have also created an article on this topic and covered this topic in this and this courses.
Though the question is looking for setting htmlWebpackPlugin.options.title, the ultimate effect is changing the title of the web-page.
1. Most convenient and trivial solution
The simplest way to do this is to modify the public/index.html and hard-code the title.
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
This is the default public/index.html that is generated by vue cli. And in this, you just need to change
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
to
<title>Title of your choice</title>
2. Change the name field in package.json
Another simple solution is to change the "name": "your-project-name". However, there are many restrictions on the name you can use in package.json. You can read more about this here. Basically, package.json must contain a name and that must be lowercase and one word, and may contain hyphens and underscores.
3. Using pages field in vue.config.js
vue.config.js is an optional file that you can add to provide additional configurations to Vue CLI and this file, if present, will be automatically loaded by Vue CLI. You need to create vue.config.js in the root folder - the folder containing you package.json file.
According to Vue documentation, you can use pages field to define entrypoint for multi-page app. However, you can also use this to define title for single page app as well. Create vue.config.js in the root directory and add pages field to your exports as follows:
module.exports = {
pages: {
index: {
// entry for the page
entry: 'src/main.js',
title: 'My Title',
},
}
}
Note that if you are already running development server, this change will be reflected only when you stop and restart the development server. In other words, these changes will not be hot reloaded.
4. Chaining Webpack
You can chain Webpack in vue.config.js as shown below
module.exports = {
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].title = "My Vue App";
return args;
})
}
}
Note that similar to solution 3, this change will be reflected only when you stop and restart the development server, in case you are already running development server. In other words, these changes will not be hot reloaded.
5. Modify title in lifecycle hooks using JavaScript
The next solution in the list is to use JavaScript to modify the title. You can do this either in mounted lifecycle hook of your root component or if you want different title for different routes, you can do the same for components loaded by each route.
<script>
export default {
data() {
return {
//
};
},
mounted() {
document.title = 'new title'
}
}
</script>
6. Use Vue Meta
Finally you can use Vue Meta to manage all metadata for your Vue app including title. First you need to add Vue Meta to your project and then use metaInfo field as shown below to configure metadata for your page or route.
{
metaInfo: {
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'My title'}
]
}
}
Conclusion
The first 4 solutions are static ways of changing your title or in other words you can't change your title at runtime using these ways. Also all of these are not hot reloaded. The last 2 options use JavaScript and can manipulate the title at runtime.
create a file vue.config.js at the root
//vue.config.js
module.exports = {
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].title = "My Vue App";
return args;
})
}
}
see https://cli.vuejs.org/guide/webpack.html#modifying-options-of-a-plugin
Update the name property in your package.json file
{
"name": "URL-friendly_app-name",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
...
},
"devDependencies": {
...
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Update:
The above mentioned method will only work if you use a URL friendly title.
There are a couple of other ways to do it
From the Vuejs official documentation Pages Configuration, you can use the html plugin configuration to specify the title for different pages
Use the environement variables Modes and Environment Variables to hold your app/page title. I personally prefer and use this method.
.env (or any .env.[mode])
VUE_APP_NAME=Application flixible name
And this is how you call it in different places in your app
AnyComponent.vue (as a data property)
TypeScript
appName: string = process.env.VUE_APP_NAME
Javascript
appName: process.env.VUE_APP_NAME
anyHTML.html
<%= process.env.VUE_APP_NAME %>
Just correcting one of the scripts with the green check
<script>
export default {
data() {
return {
//
};
}
mounted() {
document.title = 'new title'
}
}
</script>
If you are using the vue-pwa plugin, the name is set within the site.webmanifest file.
I have simply assigned value fo htmlWebpackPlugin.options.title something like this.
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title="WebStore" %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script type="module" src="https://unpkg.com/ionicons#5.5.2/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons#5.5.2/dist/ionicons/ionicons.js"></script>
</body>
</html>
Simple and I think the best way is to update the name inside the packer.json and you need to restart your app
package.json file:
{
"name": "my title app",
}
If you're using vue router you can do it through the router's index file. In the router.beforeEach method you just have to change the document.title to your routes title.
const DefaultPageTitle = "Default title"
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
meta: {
title: "Your Title"
},
},
}
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
setPageTitle(to);
next();
});
function setPageTitle(to: RouteLocationNormalized) {
const hasMetaTitleString = typeof to.meta.title == 'string';
if (!hasMetaTitleString) {
return document.title = DefaultPageTitle;
}
return document.title = to.meta.title as string;
}
At first, I was connecting to Openfire using following script.
const {Client} = require('#xmpp/client')
const client = new Client()
client.start('xmpp://localhost:5222').catch(err => {
console.error('start failed', err)
})
client.handle('authenticate', authenticate => {
return authenticate('gabbar', 'gabbar#123')
})
But it shows me an error 'require is not defined'. so I searched the internet and found that browserify could do my work. so I made the bundle.js file using index.js of my HTML page and included it in the HTML page.
<head>
<meta charset="UTF-8"/>
<title>xmpp.js example</title>
<script src="bundle.js"></script>
<!-- <script src="index.js"></script>-->
</head>
but then I am getting the error
no compatible connection method found
Is anybody can tell any other way of doing it. I tried also same as given in example directory of xmpp.js client package, but that is giving me error like XMPP is not a function. Following is the code which I wrote after looking at example files.
index.js
const {xmpp, xml} =
typeof require === 'undefined' ? window.xmpp : require('#xmpp/client') // For you; require('#xmpp/client')
const {client} = xmpp()
client.start('xmpp://localhost:5222').catch(err => {
console.error('start failed', err)
})
client.handle('authenticate', authenticate => {
return authenticate('gabbar', 'gabbar#123')
})
sample.html
<head>
<meta charset="UTF-8"/>
<title>xmpp.js example</title>
<script src="node_modules/xmpp.js/dist/xmpp.min.js"></script>
<script src="index.js"></script>
</head>
these are the two ways I tried connecting to openfire from the browser side but none of them worked for me. please, can anybody tell me what I am doing wrong or any other possible better way of doing this?
xmpp:// is not supported in the browser. Only ws:// (websockets) is supported in browser. If the server supports websockets, you would do something like:
client.start('ws://domain:port) or client.start('ws://domain:port/xmpp-websockets)
The other option is to use Node not in a browser. Which would be accomplished by running node on it's own without a browser or running that code in the background process of Electron (same as just running node by itself, but you can communicate with the renderer process to interact with a UI)
Context
I'm writing an application with Electron and Angular 2+ using Angular CLI. I've set up my electron .js file to point to the URL provided by the ng serve command, that usually is localhost:4200, in order to capture the code changes. Some considerations:
The address localhost:4200 points to index.html;
index.js is my Electron entry point script
Here is my index.js file used as an entry point for the electron module.
const {app, BrowserWindow} = require('electron');
const url = require('url');
const path = require('path');
let win = null;
function createWindow() {
win = new BrowserWindow({width: 1000, height: 600, show: false});
win.loadURL('http://localhost:4200');
win.maximize();
win.on('closed', () => {
win = null;
});
win.on('ready-to-show', () => {
win.show();
});
win.webContents.openDevTools();
}
app.on('ready', () => {
createWindow();
});
app.on('activate', () => {
if (win === null) {
createWindow();
}
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
And my index.html file:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>BRISA Carbon</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!--Clarity Design Elements-->
<link rel="stylesheet" href="../node_modules/clarity-icons/clarity-icons.min.css">
<script type="text/javascript" src="../node_modules/#webcomponents/custom-elements/custom-elements.min.js"></script>
<script type="text/javascript" src="../node_modules/clarity-icons/clarity-icons.min.js"></script>
</head>
<body>
<app-root></app-root>
</body>
</html>
My Problem
When I run ng serve, the node_modules resources inside the .html file are not being loaded and the Chrome console outputs the following messages
I know that the path to the resources are correct because I'm using WebStorm IDE and I can go to the referenced element through a link like this image below.
Why my resources are not being loaded when I'm running in Angular live mode?
For everyone that is having this same problem, I just found a solution. Instead of loading my resources through the index.html file, I've placed them in the .angular-cli.json. Basically, Angular 2+ has the own way of importing resources and seems that is not correct loading resources from the main .html file.
Well, for scripts (I mean, .js files), I'm placing it in the scripts array and styles inside the styles array. The .angular-cli.json file section that I've changed looks like this:
"styles": [
"styles.css",
"../node_modules/clarity-icons/clarity-icons.min.css",
"../node_modules/clarity-ui/clarity-ui.min.css"
],
"scripts": [
"../node_modules/#webcomponents/custom-elements/custom-elements.min.js",
"../node_modules/clarity-icons/clarity-icons.min.js"
]
Hope that this information will help someone else. For me everything is working just fine.
I assume you're experiencing the same problem when you're visiting localhost:4200 in your browser also?
In your index.html you're referencing to ../node_modules/ ... which works in your IDE because that's how the folder structure looks like.
Let's say your folder structure looks like this:
node_modules/
src/
And src is the folder that your server's using as root. Then the server wont be able to retrieve files from .. (since a root dir has no parent dir).
Instead of referencing to the JS files in your index.html, you should import/require them in your other JS files. Exactly how you do that depnds on your building/bundling tools (eg Webpack and/or Babel).
import '#webcomponents/custom-elements/custom-elements.min.js'
import 'clarity-icons/clarity-icons.min.js'
import 'clarity-icons/clarity-icons.min.css' // Assumes that you use webpack and webpack-css-loader, for example