Electron: как разрешить URL-адрес CSS в продукте?
Я хочу запустить приложение Angular с Electron на основе этого примера на моем Raspberry Pi. Приложение хорошо работает на моем ПК сnpm start
но он не может разрешить активы на Pi.
Активы определены в theme.scss
файл в src/
каталог:
$body-background-color: white;
$body-background-image: url("/assets/images/bg-hexa-gray-flat.png");
$body-background-position: top left;
$body-background-repeat: repeat;
$sidepanel-background-color: white;
$sidepanel-background-image: url("/assets/images/bg-hexa-red-flat.png");
$sidepanel-background-position: top left;
$sidepanel-background-repeat: repeat;
$sidepanel-logo: url("/assets/svg/logo.svg");
$sidepanel-logo-white: url("/assets/svg/white.svg");
и этот файл импортирован в большинство моих компонентов.
Мой main.ts
файл:
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
import * as url from 'url';
let win: BrowserWindow = null;
const serve = process.argv.slice(1).some(val => val === '--serve');
const createWindow = async () => {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 480,
icon: path.join(__dirname, 'src/assets/images/icon.png'),
webPreferences: {
nodeIntegration: true,
allowRunningInsecureContent: (serve) ? true : false,
},
});
if (serve) {
require('electron-reload')(__dirname, {
electron: require(`${__dirname}/node_modules/electron`)
});
win.loadURL('http://localhost:4200');
} else {
win.setFullScreen(true);
win.loadURL(url.format({
pathname: path.join(__dirname, 'dist/index.html'),
protocol: 'file:',
slashes: true
}));
}
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null;
});
return win;
};
try {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => app.quit());
} catch (e) {
// Catch Error
// throw e;
}
Проблема в том, что с serve
на моем ПК /assets
путь относительно корня приложения (http://localhost:4200/), и он работает нормально, но в продукте я получаю сообщение об ошибкеfile:///assets/...
не найден. Если я изменю пути в CSS пакета на моем Pi с/assets/...
к assets/...
оно работает. Если я соответствующим образом изменю его вtheme.scss
файл, он не компилируется, потому что путь относительный и theme.scss
импортируется во многие компоненты на разных уровнях дерева файлов.
Я уже пробовал такие пути, как ~/assets/...
но он составлен как /./assets/...
и для меня это не имеет смысла...
Как я могу сделать /assets
стать assets/
в пакете CSS или сделайте assets/
быть разрешенным относительно корня приложения во время компиляции?
Редактировать:
Разрешение активов в url()
это старый выпуск Angular.
2 ответа
Обновленный ответ:
ЭЛЕКТРОННЫЕ HTML-РЕСУРСЫ
В созданном приложении у вас есть папка, содержащаяindex.html
Обычно файл выглядит примерно так, и загрузка начинается с файла index.html:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>My Demo App</title>
</head>
<body>
<div id='root' class='container'></div>
<script type='module' src='vendor.bundle.js'></script>
<script type='module' src='app.bundle.js'></script>
</body>
</html>
ПРОЦЕСС СОЗДАНИЯ
Обычно в приложении Electron вы используете webpack или аналогичный упаковщик для создания вышеуказанных JS-файлов, что приводит к такому результату. Процесс сборки также может копировать активы, такие как изображения, из папки:
index.html
main.bundle.js
app.bundle.js
vendor.bundle.js
assets/images/*.png
ПРИМЕР ПРИЛОЖЕНИЯ
Чтобы сравнить с чем-то, посмотрите мой пример кода Electron, который написан на React и Typescript, хотя принципы будут такими же для Angular. Выполнение этой команды создает папку:
git clone https://github.com/gary-archer/oauth.desktopsample.final
cd oauth.desktopsample.final
./start.sh
Обратите внимание, что мое приложение создано с использованием этих сценариев веб-пакета для основного процесса Electron и процесса рендеринга, и что задействованы две разные цели:
- target: 'электрон-основной'
- цель: 'веб'
ЗАГРУЗКА ИЗОБРАЖЕНИЙ БЕЗ SASS
Быстрым способом загрузки изображений без SASS было бы изменение одного из моих представлений для прямой загрузки каждого файла ресурса:
function render() {
const myStyle={
backgroundImage: `url(./assets/images/red.png)`,
};
return (
<div style={myStyle}>
...
</div>
)
}
ЗАГРУЗКА ИЗОБРАЖЕНИЙ ИЗ ПЕРЕМЕННЫХ SASS
Для этого требуется выполнить ряд шагов, чтобы сначала установить эти зависимости:
npm install --save-dev node-sass
npm install --save-dev style-loader
npm install --save-dev css-loader
npm install --save-dev sass-loader
Затем обновите webpack, чтобы сообщить ему, как обрабатывать файлы SCSS:
{
test: /\.(scss)$/,
use: [
// Creates style nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles SASS to CSS
'sass-loader',
],
exclude: /node_modules/
}
Затем предоставьте файл, напримерapp.module.scss
содержащий стили и переменные для экспорта:
$body-background-image: url('./assets/images/red.png');
// Export variables
:export {
bodyBackgroundImage: $body-background-image;
}
Затем вам нужно будет обновить код React следующим образом. Обратите внимание, что имя файла, оканчивающееся наmodule.scss
может потребоваться, чтобы предотвратить импорт, приводящий к нулевому объекту:
import styles from '../../app.module.scss';
function render() {
const myStyle={
backgroundImage: styles.bodyBackgroundImage,
};
return (
<div style={myStyle}>
...
</div>
)
}
Также предотвратите обработку Typescript элементов SCSS как типов, добавивtypings.d.ts
файл в папку.
declare module '*.scss';
Вам также потребуется создатьsrc/assets/images/red.png
файл и изменить мойbuild.sh
скрипт для копирования ресурсов в папку dist:
mkdir dist
cp index.html desktop*.json *.css package.json src/preload.js dist
mkdir dist/assets
mkdir dist/assets/images
cp src/assets/images/*.png dist/assets/images
Как только все это будет сделано, перезапуститеstart.sh
скрипт и интеграция SCSS будут работать из папки.
РАЗВЕРНУТОЕ ПРИЛОЖЕНИЕ
Вы также можете встроить мое приложение в упакованный исполняемый файл, подобный этому, и активы будут продолжать загружаться:
./pack.sh
В конечном итоге пути к ресурсам просто транслируются из файла SCSS и записываются в пакеты Javascript, как в моем первом примере выше. Эти пути относятся к папке, из которой запускается конечное приложение, представленное папкой.
const myStyle={
backgroundImage: `url(./assets/images/red.png)`,
};
КРАТКОЕ СОДЕРЖАНИЕ
Обычно у вас есть несколько режимов выполнения, первый из которых поддерживает продуктивную локальную разработку и загружает активы из папки, а также использует такие инструменты, как электронная перезагрузка. Я бы рекомендовал реализовать ранний процесс сборки, который больше не используетsrc
папка. Вместо этого соберите все ресурсы HTML вdist
папка, которую использует развернутая версия приложения.
Я написал об этом сообщение в блоге, так как хотел дать хорошую рекомендацию после того, как увидел предложения, которые не заботились о безопасности или казались слишком сложными . У меня есть шаблон Github со встроенным кодом, поэтому вы можете увидеть его в рабочем примере ->secure-electron-template
.
Короче говоря, чтобы поддерживать относительную загрузку изображений в средах разработки и производства, следуя моим инструкциям, вам необходимо:
- Используйте [по крайней мере] Webpack 5 (я надеюсь, что Webpack продолжит поддерживать модули активов в будущих версиях Webpack..)
- Используйте сервер разработки Webpack для локальной разработки Electron.
Если эти условия соблюдены, необходимо определить модуль активов в файле Webpack.config.js, чтобы загружать форматы изображений.
// loads common image formats
{
test: /\.(svg|png|jpg|gif)$/,
include: [
path.resolve(__dirname, "resources/images")
],
type: "asset/inline"
}
include должен указать путь, по которому сохраняются все ваши изображения.
«Актив/встроенный» экспортирует изображение как URI данных. (Если вы используете версию Webpack до 5, я предполагаю, что вы можете использоватьurl-loader
вместо модуля активов)
Загрузка изображений в сборках разработки теперь работает путем импорта изображения.
import React from "react";
import img from "../[RelativePathToImagesFolder]/images/testimage.png";
class Image extends React.Component {
render() {
return (
<React.Fragment>
<img src={img} />
</React.Fragment>
);
}
}
export default Image;
Если вы хотите, чтобы ваши образы продолжали работать в производственных сборках, вам необходимо определить/зарегистрировать собственный протокол , так как по умолчанию Electron загружает ресурсы черезfile://
схема, которая не позволяет загружать ресурсы, если не отключен BrowserWindow.webPreferences.webSecurity ( что небезопасно). Вы можете начать с моей пользовательской схемы , а затем зарегистрировать ее в своем файле main.js.
main.js
const {
app,
protocol,
BrowserWindow
} = require("electron");
const Protocol = require("./protocol");
const isDev = process.env.NODE_ENV === "development";
const port = 40992; // Hardcoded; needs to match webpack.development.js and package.json
const selfHost = `http://localhost:${port}`;
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
async function createWindow() {
if (!isDev) {
// Needs to happen before creating/loading the browser window;
// protocol is only used in prod
protocol.registerBufferProtocol(Protocol.scheme, Protocol.requestHandler);
}
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600
});
// Load app
if (isDev) {
win.loadURL(selfHost);
} else {
win.loadURL(`${Protocol.scheme}://rse/index.html`);
}
// Emitted when the window is closed.
win.on("closed", () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null;
});
}
// Needs to be called before app is ready;
// gives our scheme access to load relative files,
// as well as local storage, cookies, etc.
// https://electronjs.org/docs/api/protocol#protocolregisterschemesasprivilegedcustomschemes
protocol.registerSchemesAsPrivileged([{
scheme: Protocol.scheme,
privileges: {
standard: true,
secure: true
}
}]);
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);
// Quit when all windows are closed.
app.on("window-all-closed", () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow();
}
});
Предполагая, что вы тоже используетеHtmlWebpackPlugin
, вам также необходимо определить базовое свойство (которое добавляет элемент в ваш связанный html-файл) к схеме, которую вы определили, чтобы все относительные URL-адреса правильно разрешались в рабочей среде.
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "app/src/index.html"),
filename: "index.html",
base: "app://rse"
})