quasarframework / quasar

Quasar Framework - Build high-performance VueJS user interfaces in record time
https://quasar.dev
MIT License
25.95k stars 3.52k forks source link

From @quasar/app v3.0.0-beta.16, Show compile error Can't resolve imported dependency "crypto" #9175

Closed baryon closed 3 years ago

baryon commented 3 years ago

Describe the bug

My electron app use crypto lib UI, it works @quasar/app v3.0.0-beta.15

BUT, it don't work from @quasar/app v3.0.0-beta.16 ~ 20 shows some compile error

[19:58:17] Starting 'desktop'...

 Dev mode.......... electron
 Pkg quasar........ v2.0.0-beta.12
 Pkg @quasar/app... v3.0.0-beta.16
 Pkg webpack....... v5
 Debugging......... enabled

 Configured browser support (at least 87.68% of global marketshare):
 · Chrome for Android >= 90
 · Firefox for Android >= 87
 · Android >= 90
 · Chrome >= 80
 · Edge >= 87
 · Firefox >= 79
 · iOS >= 11.0-11.2
 · Opera >= 71
 · Safari >= 11.1

 App • Chaining "Renderer" Webpack config
 App •  WAIT  • Compiling of "Renderer" in progress...
 App •  DONE  • "Renderer" compiled with errors • 22614ms

 App •  ERROR  •  Renderer  in ./node_modules/bsv/lib/crypto/hash.node.js

Module not found: Can't resolve imported dependency "crypto"
Did you forget to install it? You can run: yarn add crypto

 App •  ERROR  •  Renderer  in ./node_modules/bsv/lib/mnemonic/pbkdf2.js

Module not found: Can't resolve imported dependency "crypto"
Did you forget to install it? You can run: yarn add crypto

 App •  ERROR  •  Renderer  in ./node_modules/dotwallet/src/utils.js

Module not found: Can't resolve imported dependency "crypto"
Did you forget to install it? You can run: yarn add crypto

 App •  ERROR  •  Renderer  in ./node_modules/abort-controller/polyfill.mjs

Module not found: Can't resolve imported dependency "./dist/abort-controller"

 App •  COMPILATION FAILED  • Please check the log above for details.

Platform (please complete the following information): Quasar Version: v2.0.0-beta.12 @quasar/app Version: @quasar/app v3.0.0-beta.16 ~ 20 Quasar mode:

Tested on:

OS: Mac OS Node: 12 NPM: Yarn: Browsers: iOS: Android: Electron: 12

hawkeye64 commented 3 years ago

@baryon Have you tried removing node_modules and yarn.lock and re-installing?

koernchen02 commented 3 years ago

I had kind of the same "issue"... Maybe the following does help:

Since the most recent quasar version uses webpack 5 :fireworks:
There are no polyfills for node.js (core) modules any more.

I solved those kind of problems by providing crypto (and other core) accesses via an sanitized api and electrons context bridge!

baryon commented 3 years ago

@baryon Have you tried removing node_modules and yarn.lock and re-installing?

Tried it many times. Same issue.

rstoenescu commented 3 years ago

@baryon Can you supply a minimal reproduction repo pls? So we can understand what the problem is.

TobyMosque commented 3 years ago

crypto (as well any electron/node module) would be exposed/handled by your preload script: Read more on: https://next.quasar.dev/quasar-cli/developing-electron-apps/electron-preload-script

here a example, where I'm handling electron and node stuff: src-electron/electron-preload.ts

import { ipcRenderer, contextBridge, App } from 'electron';
import fs from 'fs';
import crypto from 'crypto';

type GetPath = App['getPath'];
const getPath: GetPath = (name) => {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
  const result = ipcRenderer.sendSync('remote-get-path', name);
  return result as string;
};

const preload = {
  // probably u don't wanna to expose the whole fs module here
  // but if some of your dependencies relly on it, u'll be forced to do that.
  node: {
    fs() {
      return fs;
    },
    crypto() {
      return crypto;
    },
  },
  electron: {
    getPath,
  },
};

contextBridge.exposeInMainWorld('preload', preload);

src-electron/electron-main.ts

import { app, BrowserWindow, ipcMain } from 'electron';
import path from 'path';

function createWindow() {
  mainWindow = new BrowserWindow({ /*....*/ })

  ipcMain.on('remote-get-path', (event, path) => {
    event.returnValue = app.getPath(path)
  });
}

if u're using typescript, don't forget to declare the types:

src/global.d.ts

export {};

declare global {
  interface Window {
    preload: {
      node: {
        fs: () => typeof import('fs');
        crypto: () => typeof import('crypto');
      };
      electron: {
        getPath: import('electron').App['getPath'];
      };
    };
  }
}

so, u can access them through the window.preload.node path/to/somewhere.ts

export default {
  setup () {
    // even if now u're supporting only the electron mode, I highly recommend you to write universal code.
    // in this case, interfaces and dependency injection are u friend, just declare the interface (and/or an abstract class)
    // so implement and register the service based on that interface in a boot what are registered only at the electron mode.
    // so if u need add support to other module, all what u need to do, is add a new boot with the desired implementation
    if (process.env.MODE === 'electron') {
      const { getPath } = window.preload.electron
      const { fs, crypto } = window.preload.node
    }
  }
}

in the case of that packages are required by 3th party libs, like serialport, aws-polly, etc, u'll need to declare the needed libs as a external (so the packages will make use of the preloaded packages):

quasar.config.js

module.exports = configure(function (ctx) {
  return {
    build: {
      chainWebpack (chain) {
        if (ctx.mode.electron) {
          chain.merge({
            externals: {
              fs: 'window.preload.node.fs()',
              crypto: 'window.preload.node.crypto()'
            }
          })
        }
      }
    }
  }
}

registering that externals, u'll be able to import that packages directly in your code, but I think that is a little hacky:

import fs from 'fs'
import crypto from 'crypto'
xollaborator commented 3 years ago

It is not important here but I am not using electron mode (SPA/PWA instead). I am using dropbox sdk (yarn add dropbox) and just installing it might be enough to reproduce the error (which is related to webpack 5). I had below error for two packages - crypto and util:

App •  ERROR  •  UI  in ./node_modules/dropbox/dist/Dropbox-sdk.min.js

Module not found: Can't resolve imported dependency "crypto"

To solve the issue I had to run yarn add -D node-polyfill-webpack-plugin and configure the plugin in quasar.conf.js:

const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
module.exports = configure(function (ctx) {
  return {
    build: {
      extendWebpack(cfg) {
        cfg.plugins.push(new NodePolyfillPlugin({}));
      }
    }
  }
}

The solution is based on https://github.com/dropbox/dropbox-sdk-js/issues/614.

baryon commented 3 years ago

Want a solution that doesn't require additional setup

koernchen02 commented 3 years ago

There will not be a solution without "additional setup"... The webpack5 change is real! And what @TobyMosque recommended is the way to go. crypto, path, etc should not be available to the renderer.

baryon commented 3 years ago

@baryon Can you supply a minimal reproduction repo pls? So we can understand what the problem is.

wrote a demo project to reproduce this issue

https://github.com/baryon/helloquasar

rstoenescu commented 3 years ago

@baryon

Took a look at your repo (thanks for supplying one!). A few things, due to how newer Electron versions require you to handle the app (for security reasons):

  1. If you need to access nodejs stuff from within the Renderer thread (your UI, /src), you will need to supply it from the preload script (/src-electron/electron-preload.js).
  2. No way around it AND still be Electron future bulletproof. Again, for security reasons, you'll definitely want to stay up to date with latest Electron.
  3. Having your nodejs logic in the preload script as an Electron requirement is better exposed through Webpack 5, because Webpack 5 does not ships with node polyfills anymore. However you can use node-polyfill-webpack-plugin package (as suggested above already).

So, how to adapt your repo to the latest Electron specifications (AND not requiring any additional webpack setup):

// src-electron/electron-preload.js

import { contextBridge } from 'electron'
import bsv from 'bsv'

contextBridge.exposeInMainWorld('myAPI', {
  getBsvAddress: () => {
    const privkey = bsv.PrivateKey.fromRandom()
    return privkey.toAddress().toString()
  }
})
// src/pages/Index.vue

<script>
import { defineComponent } from 'vue';
import { ref } from 'vue'

export default defineComponent({
  name: 'PageIndex',
  setup() {
    return {
      address: ref(window.myAPI.getBsvAddress()) // <<<<<<<<<<<<<<
    }
  }
})
</script>

As a general rule of thumb, if you care about security and not working around (and against) Electron best practices then do not directly expose nodejs packages through the preload script. Instead, expose methods that themselves use these nodejs packages (do NOT expose nodejs packages methods directly).

baryon commented 3 years ago

Thanks for your reply. The app will be release with capacitor for mobile. It have same issue too. Welcome Any suggestions

dsl101 commented 3 years ago

This worked for me for directly including crypto in a component (e.g. import md5 from 'crypto-js/md5':

  1. yarn add crypto-js
  2. Edit quasar.conf.js:
      extendWebpack (cfg) {
        cfg.resolve.fallback = { crypto: false }
      },
xollaborator commented 3 years ago

Thanks @dsl101, I wasn't satisfied with adding polyfills in my setup but just setting crypto fallback to false (without adding crypto-js/md5) as presented above seems to work fine for my SPA/PWA setup. @rstoenescu I still need polyfills for Cypress config. I guess they might be needed for SSR and other modes. Seems like a feature request but adding polyfills/setting fallbacks based on mode might solve few problems for quite a lot of people. I didn't have time to go deeper into Webpack configuration but items like this one allowed me to experiment with different options and reach the state in which I am satisfied with Quasar v2.

TobyMosque commented 3 years ago

I don't think it would be done on the Quasar side, I think the polyfills got removed for a good reason. Even if we add a optional flag, like quasar.config.js > build > node > polyfill: true, that can pass the message the polyfills got removed only to same some bytes, but in the true, you're getting a lot of risks by doing that.

But if u know u risks, u can install it.: https://www.npmjs.com/package/node-polyfill-webpack-plugin

xollaborator commented 3 years ago

Hi @TobyMosque, thank you for the reply. I am aware that there are a lot of risks related to each package and those even include cases like using Quasar. It is not easy to balance DX vs security but I trust you make better decision than I would since you know the ecosystem a lot better than me. In the end, I wanted to share what problems and possible solutions I have encountered so far using Quasar v2 (and Webpack 5 indirectly) and let you know where is the potential to improve DX. This is still the only item I've found related to webpack no longer polyfilling node packages so I guess others might find some useful information here.