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.

Короче говоря, чтобы поддерживать относительную загрузку изображений в средах разработки и производства, следуя моим инструкциям, вам необходимо:

  1. Используйте [по крайней мере] Webpack 5 (я надеюсь, что Webpack продолжит поддерживать модули активов в будущих версиях Webpack..)
  2. Используйте сервер разработки 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"
})
Другие вопросы по тегам