skpm / sketch-module-web-view

A sketch module for creating an complex UI with a webview
MIT License
289 stars 62 forks source link

"Not allowed to load local resource" with VueJs #174

Closed abbrechen closed 3 years ago

abbrechen commented 3 years ago

Hi,

when I upgraded the webview template with VueJs, the plugin / Safari is not allowed to load my main.js file. I know there is a working vue-webview but it's outdated (Sketch API syntax, sass-loader compatibility). I don't get any error while compiling, just the webview console error: Not allowed to load local resource: file:///Users/USERNAME/PATH/newWebviewTest/newwebviewtest.sketchplugin/Contents/Resources/main.js

System: macOS Catalina, Version 10.15.7 Sketch: 71.2

Project Structure

project structure skpm webview vue

webpack.skpm.config.js

module.exports = function (config, entry) {
  config.node = entry.isPluginCommand ? false : {
    setImmediate: false
  };
  config.module.rules.push({
    test: /\.(html)$/,
    use: [{
        loader: "@skpm/extract-loader",
      },
      {
        loader: "html-loader",
        options: {
          attrs: [
            'img:src',
            'link:href'
          ],
          interpolate: true,
        },
      },
    ]
  })
  config.module.rules.push({
    test: /\.(scss)$/,
    use: [
      {
        loader: 'style-loader',
      },
      {
        loader: 'css-loader',
      },
      {
        loader: 'sass-loader'
      }
    ],
  })
  config.module.rules.push({
    test: /\.(png|jpg|gif|svg|sketch)$/,
    loader: 'file-loader',
    options: {
      name: '[name].[ext]?[hash]',
    },
  })
  config.module.rules.push({
    test: /\.vue$/,
    use: 'vue-loader',
  })
  config.resolve = {
    extensions: ['.js', '.vue', '.json'],
  }
}

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>New Plugin</title>
</head>

<body>
  <div id="app"></div>
  <script src="../main.js"></script>
</body>

</html>

my-command.js

import BrowserWindow from 'sketch-module-web-view'
import { getWebview } from 'sketch-module-web-view/remote'
import UI from 'sketch/ui'

const webviewIdentifier = 'newwebviewtest.webview'

export default function () {
  const options = {
    identifier: webviewIdentifier,
    x: 0,
    y: 0,
    width: 420,
    height: 504,
    blurredBackground: true,
    onlyShowCloseButton: true,
    hideTitleBar: false,
    shouldKeepAround: true,
    AlwaysReloadScript: true
  }

  const browserWindow = new BrowserWindow(options)

  // only show the window when the page has loaded to avoid a white flash
  browserWindow.once('ready-to-show', () => {
    browserWindow.show()
  })

  const webContents = browserWindow.webContents

  // print a message when the page loads
  webContents.on('did-finish-load', () => {
    UI.message('UI has loaded')
  })

  // add a handler for a call from web content's javascript
  webContents.on('nativeLog', s => {
    UI.message(s)
    webContents
      .executeJavaScript(`setRandomNumber(${Math.random()})`)
      .catch(console.error)
  })

  browserWindow.loadURL(require('../resources/index.html'))
}

// When the plugin is shutdown by Sketch (for example when the user disable the plugin)
// we need to close the webview if it's open
export function onShutdown() {
  const existingWebview = getWebview(webviewIdentifier)
  if (existingWebview) {
    existingWebview.close()
  }
}

manifest.json

{
  "compatibleVersion": 3,
  "bundleVersion": 1,
  "commands": [
    {
      "name": "my-command",
      "identifier": "newwebviewtest.my-command-identifier",
      "script": "./my-command.js",
      "handlers": {
        "run": "onRun",
        "actions": {
          "Shutdown": "onShutdown"
        }
      }
    }
  ],
  "menu": {
    "title": "newWebviewTest",
    "items": [
      "newwebviewtest.my-command-identifier"
    ]
  }
}

package.json

{
  "name": "newwebviewtest",
  "version": "0.1.0",
  "engines": {
    "sketch": ">=3.0"
  },
  "skpm": {
    "name": "newWebviewTest",
    "manifest": "src/manifest.json",
    "main": "newwebviewtest.sketchplugin",
    "assets": [
      "assets/**/*"
    ],
    "sketch-assets-file": "sketch-assets/icons.sketch"
  },
  "scripts": {
    "build": "skpm-build",
    "watch": "skpm-build --watch",
    "start": "skpm-build --watch",
    "postinstall": "npm run build && skpm-link"
  },
  "devDependencies": {
    "@skpm/builder": "^0.7.4",
    "@skpm/extract-loader": "^2.0.2",
    "css-loader": "^3.6.0",
    "html-loader": "^0.5.5",
    "less": "^4.1.1",
    "less-loader": "^8.1.1",
    "sass": "^1.32.12",
    "sass-loader": "^10.2.0",
    "style-loader": "^2.0.0",
    "vue-loader": "^14.2.4",
    "vue-template-compiler": "^2.6.12"
  },
  "resources": [
    "resources/**/*.js"
  ],
  "dependencies": {
    "sketch-module-web-view": "^3.1.4",
    "vue": "^2.6.12",
    "vue-router": "^3.5.1",
    "vuex": "^3.6.2"
  },
  "author": ""
}

If anyone knows why I can not load my main.js file, thanks a lot.

abbrechen commented 3 years ago

Ok, the problem was behind the keyboard. The main issue was that my js file needs the prefix "resources" because after the bundling the files get a "resources" prefix and the index.html is not part of that. Two other problems you will maybe face when integrating VueJs are

  1. The error message TypeError: this.getOptions is not a function This issue depends on the sass-loader version you are using. When I installed the sass-loader it was automatically v11. I downgraded to ^10.2.0 and that worked.

  2. VueLoaderPlugin When I used the vue-loader v15 I got an error. Unfortunately, I can't remember the specific text. But I downgraded my loader to ^14.2.4 and then it was fine.

A secondary issue that is similar to the main problem of this post is when I switch the route, the plugin tries to open a file. The routing itself works, so I will work later on this issue. The error message is: Not allowed to load local resource: file:///routeName (*routeName = placeholder for the route name).

lse-dvh commented 2 years ago

A secondary issue [...] the plugin tries to open a file. [...] The error message is: Not allowed to load local resource: file:///routeName (*routeName = placeholder for the route name).

I finally found a solution on how to load images in Vue components in the webview.

Explanation

VueJS itself does not allow to load local files, due to security reasons. I think if you run Vue on a server, you don't have this issue. But the webview in Sketch plugins doesn't start a webserver. So the solution is to use url-loader instead of file-loader.

The url-loader inlines images and other files as base64 (= bytes of the image get encoded as string directly into the document):

<!-- default plugin logo 'icon.png' as local file -->
<img src="./assets/icon.png">

<!-- default plugin logo 'icon.png' as base64-->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAYFBMVEVHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzK7FeAAAAIHRSTlMAAQQJDRETFRcaBgwPAiIecIyKTUhUK31eLzMqQ3J5hK0cHJgAAAI4SURBVHgB7NTXFUMhDAPQYKoo+6+b/Kcbve67gG1xxM0YY4wxxhyUEx9iygUPJacYvLj1hteW8EJqdY0lai94q/S67PTRMr7IbSw3PuAnYWw6fqkVfMEfiiePl4Q/JaGeDwVeCK5DpTvO/JGhlAdjvhSoFeHM33IDwSTZ8H5CBqNgWhkT/csgyPo2dlB0/f9H4gkF2KIKCTRJ9wBEfoMGznYxgCr8HQDIxrYBAGHLABQRNNC1vxbIeOHOnnksOQjEULDIyOSc4f+/cgPh4jhUM3vavjq1AZVGT/GKPKL0Hu/U/CWPJGm2khfykiLPVtJEHrnBLlRmB7G8IM4OStaTDF8eqbKDWl5QZweVPOIb6A5wgRP3IOACrA5cLoB6oiF6BMRAJxEuoHwusXQJWKgRcQHlhuToEnBIEXAB9TLwLhEA/cjXJeArCoguAfkXQALFmfNAgQR8eUbcZButvKTNNprnjj4qQ0m6/pdB3jCsb+kSQWXoCobNqI5owmFTEcdmgznH+rtohAUlhijSjSudKGKwcOb1qFKSIgCj6XQITFePpzdRYj4EZlHixkYzLOAbbDjFAiEcz7HADQcUTMDjEQ0TCHhIxQQiHtMhAZsHlUwg4lEtErBBWA0E0OLIulLA4gsLJODylQ0TMPnSCglYfG2HBEJdi8tp2Zl0LS6/27VjAQAAAABh/tZh7CmNxtetn9d+33vA4AkHQCyA8XjI5CmXx2ye83nQ6EknQK2e9XrY7Lu7u7u7AKyW+neuBv8iAAAAAElFTkSuQmCC">

Keep in mind that there is a size limit for files handled by url-loader. If the file exceeds the limit, the fallback is to use file-loader (which will break the image loading again). You can adjust this value in webpack.skpm.config.js(see https://v4.webpack.js.org/loaders/url-loader/#limit).

Also keep in mind that images only seem to get processed in the build step. So make sure to repeatedly run npm run watch (after adding images, fonts or other local files with src or inside css url()).

Solution

directory structure: (only relevant files shown)

resources/
    assets/
        icon.png
    App.vue
    app.js
    index.html
    style.css
package.json
webpack.skpm.config.js

package.json:

{
  "devDependencies": {
    "@skpm/builder": "^0.8.0",
    "@skpm/extract-loader": "^2.0.3",
    "css-loader": "^3.6.0",
    "html-loader": "^0.5.5",
    "node-sass": "^6.0.1",
    "sass-loader": "^10.2.1",
    "url-loader": "^4.1.1",
    "vue-loader": "^15.9.8",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.6.14"
  },
  "dependencies": {
    "sketch-module-web-view": "^3.5.1",
    "vue": "^2.6.14"
  }
}

webpack.skpm.config.js:

// We can use 'entry.isPluginCommand' to check, if it is a normal JavaScript file (not a command).
// based on https://github.com/skpm/with-webview/blob/master/template/webpack.skpm.config.js
const { VueLoaderPlugin } = require('vue-loader');

module.exports = function (config, entry) {
  config.node = entry.isPluginCommand ? false : {
    setImmediate: false
  };
  config.module.rules.push({
    test: /\.(html)$/,
    use: [
      { loader: "@skpm/extract-loader" },
      {
        loader: "html-loader",
        options: {
          attrs: ['img:src', 'link:href'],
          interpolate: true,
        },
      },
    ]
  });
  config.module.rules.push({
    test: /\.(css)$/,
    use: [
      { loader: "@skpm/extract-loader" },
      { loader: "css-loader" },
    ]
  });
  config.module.rules.push({
    // see https://vue-loader.vuejs.org/guide/pre-processors.html#sass
    test: /\.scss$/,
    use: [
      'vue-style-loader',
      'css-loader',
      'sass-loader'
    ]
  });
  config.module.rules.push({
    test: /\.(png|jpg|gif|svg|sketch)$/,
    loader: 'url-loader',
    options: {
      name: '[name].[ext]',
      esModule: false, // see https://stackoverflow.com/a/59075858/webpack-file-loader-outputs-object-module
    },
  });
  config.module.rules.push({
    test: /\.vue$/,
    use: "vue-loader",
  })
  config.resolve = {
    extensions: ['.js', '.vue', '.json'],
  };
  if (config.entry && !entry.isPluginCommand) {
    config.plugins.push(
      new VueLoaderPlugin()
    );
  }
}

app.js:


import Vue from 'vue';
import App from './App.vue';

new Vue({
  el: '#app',
  render: h => h(App),
});

App.vue:

<template>
  <div id="app">
    <img src="./assets/icon.png">
    <h1>\{{ msg }}</h1>
</template>

<script>
export default {
  name: 'app',
  data() {
    return {
      msg: 'Welcome to Your Vue.js App',
    };
  },
};
</script>

<style lang="scss">
#app {
  width: 240px;
  height: 180px;
  background-image: url("./assets/icon.png");
}
</style>

index.html:

<!DOCTYPE html>
<html lang="en">

<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">
  <title>Webview with Vue</title>
  <link rel="stylesheet" href="./style.css" />
</head>

<body>
  <div id="app"></div>
  <script src="../resources_app.js"></script>
</body>

</html>