I am building a svelte browser extension, and in order to load images, I found out that I can use chrome.runtime.getURL(relative_path), which is well supported across browsers.
So, I wrote this code:
<img
src={chrome.runtime.getURL("image.png")}
alt={"an image"}
/>
When trying to build, I get:
transforming (8) node_modules/.pnpm/svelte#3.55.1/node_modules/svelte/transition/ind13:09:26 [vite-plugin-svelte] /MyComponent.svelte:17:9 'chrome' is not defined
15: <button on:click={handleOnClick}>
16: <img
17: src={chrome.runtime.getURL("image.png")}
^
18: alt={"an image"}
19: />
✓ 25 modules transformed.
I tried to polyfill this using webextension-polyfill and #types/chrome npm packages with no success.
My workaround:
I created this function
const polifillImg = (img: string) => {
// At build time this will be replaced by chrome.runtime.getURL(imgSrcBase + img)
return (
"__REPLACE_WITH_CHROME__" + imgSrcBase + img + "__CLOSING_PARENTESIS__"
);
};
Which I can now use like so:
<img
src={polifillImg("image.png")}
alt={"an image"}
/>
And in my vite.config.ts:
import { defineConfig } from "vite";
import { svelte } from "#sveltejs/vite-plugin-svelte";
import { replaceCodePlugin } from "vite-plugin-replace";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
replaceCodePlugin({
replacements: [
{ from: '"__REPLACE_WITH_CHROME__" + ', to: "chrome.runtime.getURL(" },
{ from: ' + "__CLOSING_PARENTESIS__"', to: ")" },
],
}),
svelte(),
],
});
Which, you know, works, but it is not great.
I would like to know how to correctly use the chrome object the right way. Can someone show me the way?
Thanks
Related
I'm now using vuepress2 with quasar 2.7.1 like this:
import { Quasar } from 'quasar';
export default defineClientAppEnhance(({ app, router, siteData }) => {
app.use(Quasar);
}
#import url(https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons|Material+Icons+Outlined);
#import 'quasar/src/css/variables.sass';
#import 'quasar/src/css/core/colors.sass';
.quasar-comp {
#import 'quasar/src/css/index.sass';
}
/* I wrap the custome component in class .quasar-comp
so that the style from quasar won't conflict with style from vuepress. */
but there are 2 issues:
The style from quasar cannot works on some components, like q-btn-dropdown or q-menu.
It works well on dev mode(npm run docs:dev), but failed to build(npm run docs:build).
✔ Compiling with vite - done
✖ Rendering pages - failed
TypeError: Cannot convert undefined or null to object
at Function.assign (<anonymous>)
at installQuasar (/Users/lxm/Documents/neo/leaneo-docs/node_modules/quasar/dist/quasar.cjs.prod.js:6:15228)
at Object.install (/Users/lxm/Documents/neo/leaneo-docs/node_modules/quasar/dist/quasar.cjs.prod.js:6:479348)
at Object.use (/Users/lxm/Documents/neo/leaneo-docs/node_modules/#vue/runtime-core/dist/runtime-core.cjs.prod.js:3393:28)
at /Users/lxm/Documents/neo/leaneo-docs/docs/.vuepress/dist/.server/app.js:3745:7
at createVueApp (/Users/lxm/Documents/neo/leaneo-docs/docs/.vuepress/dist/.server/app.js:4177:11)
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/vuepress-vite/node_modules/#vuepress/bundler-vite/lib/build/build.js:49:52
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/#vuepress/utils/lib/withSpinner.js:12:24
at async build (/Users/lxm/Documents/neo/leaneo-docs/node_modules/vuepress-vite/node_modules/#vuepress/bundler-vite/lib/build/build.js:34:5)
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/#vuepress/cli/lib/commands/build/createBuild.js:51:5
Is there a better way to make quasar and vuepress works together?
Here is the implementation in Vuepress 2 (beta) and Quasar with Typescript.
First, install Quasar into Vuepress with the new syntax and import all styles:
client.ts:
import { defineClientConfig } from "#vuepress/client";
import { Quasar } from "quasar";
import 'quasar/src/css/index.sass';
export default defineClientConfig({
enhance({app, router, siteData}) {
app.use(Quasar);
},
setup() {},
rootComponents: [],
});
Then, ignore deprecation errors from sass if you're using vite.
config.ts
import { viteBundler } from '#vuepress/bundler-vite'
import { defineUserConfig } from '#vuepress/cli'
export default defineUserConfig({
// your config
...
bundler: viteBundler({
viteOptions: {
css: {
preprocessorOptions: {
scss: {
sassOptions: {
// ignore sass deprecation errors
quietDeps: true
}
}
}
}
},
}),
});
Hope it helps :)
Quasar needs some globales to be defined (ie if it's SSR). My following solution works ONYL on client-side so be sure to wrap your custom-quasar component in <ClientOnly> !
// .vuepress/config.ts
bundler: viteBundler({
viteOptions: {
define: {
__QUASAR_VERSION__: `'dev'`,
__QUASAR_SSR__: false,
__QUASAR_SSR_SERVER__: false,
__QUASAR_SSR_CLIENT__: false,
__QUASAR_SSR_PWA__: false
}
}
}),
// .vuepress/client.ts
import { defineClientConfig } from '#vuepress/client'
import Quasar from "quasar/src/install-quasar.js";
// optionally import your styles here
// import 'quasar/src/css/index.sass';
export default defineClientConfig({
enhance({ app, router, siteData }) {
app.use(Quasar);
},
setup() {
},
rootComponents: [],
});
<!-- README.md -->
<ClientOnly>
<MyComponent />
</ClientOnly>
import React from "react";
import GoogleMapReact from "google-map-react";
const AnyReactComponent = ({ text }) => <div>{text}</div>;
export default function AppMapPage() {
const defaultProps = {
center: {
lat: 10.99835602,
lng: 77.01502627,
},
zoom: 11,
};
return (
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{ key: "my key" }}
defaultCenter={defaultProps.center}
defaultZoom={defaultProps.zoom}
>
<AnyReactComponent lat={59.955413} lng={30.337844} text="My Marker" />
</GoogleMapReact>
</div>
);
}
I have been trying to solve it for a while. I am trying to run simple example of google-map-react. But, This does not load maps. Instead gives following errors and the page is blank.
google_map_markers.js:100 Uncaught TypeError: Cannot read properties of undefined (reading 'getChildren')
at o._getState (google_map_markers.js:100:1)
at new o (google_map_markers.js:248:1)
at constructClassInstance (react-dom.development.js:13522:1)
at updateClassComponent (react-dom.development.js:20497:1)
at beginWork (react-dom.development.js:22440:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:4161:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4210:1)
at invokeGuardedCallback (react-dom.development.js:4274:1)
at beginWork$1 (react-dom.development.js:27405:1)
at performUnitOfWork (react-dom.development.js:26513:1)
To resolve these issues, you have to remove *<React.StrictMode>* in the index.js file.
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>);
To
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
it's due to versioning. I downgraded from 18 to 17 and it worked. Man sometimes open source code consumes your all day.
I Just removed the <React.StrictMode> in the index.js file and it worked for me.
Electron 3.0.0-beta.1
Node 10.2.0
Chromium 66.0.3359.181
The problem I'm having is importing a module. I created the following protocol:
protocol.registerFileProtocol('client', (request, callback) => {
var url = request.url.substr(8);
callback({path: path.join(__dirname, url)});
});
The output of the protocol is the correct path
"/Users/adviner/Projects/Client/src/ClientsApp/app.js"
I have the following module app.js with the following code:
export function square() {
return 'hello';
}
in my index.html I import the module like so:
<script type="module" >
import square from 'client://app.js';
console.log(square());
</script>
But I keep getting the error:
app.js/:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.
I'm done searches but can't seem to find a solution. Can anyone suggest a way I can make this work?
Thanks
This is a tricky question and i will refer to Electron#12011 and this GitHub Gist for a deeper explaination but the core learning is that the corresponding HTML spec, disallows import via file:// (For XSS reasons) and a protocol must have the mime types defined.
The file protocol you use client:// has to set the correct mime-types when serving the files. Currently i would guess they are not set when you define the protocol via protocol.registerBufferProtocol thus you recive a The server responded with a non-JavaScript MIME type of "", the gist above has a code sample on how to do it.
Edit: I just want to emphasize the other answers here do only cover the absolute minimum basics implementation with no consideration of exceptions, security, or future changes. I highly recommend taking the time and read trough the gist I linked.
To confirm: this is there for security reasons.
However, in the event that you just need to get it deployed:
Change "target": "es2015" to "target": "es5" in your tsconfig.json file
Quick Solution:
const { protocol } = require( 'electron' )
const nfs = require( 'fs' )
const npjoin = require( 'path' ).join
const es6Path = npjoin( __dirname, 'www' )
// <= v4.x
// protocol.registerStandardSchemes( [ 'es6' ] )
// >= v5.x
protocol.registerSchemesAsPrivileged([
{ scheme: 'es6', privileges: { standard: true } }
])
app.on( 'ready', () => {
protocol.registerBufferProtocol( 'es6', ( req, cb ) => {
nfs.readFile(
npjoin( es6Path, req.url.replace( 'es6://', '' ) ),
(e, b) => { cb( { mimeType: 'text/javascript', data: b } ) }
)
})
})
<script type="module" src="es6://main.js"></script>
Based on flcoder solution for older Electron version.
Electron 5.0
const { protocol } = require('electron')
const nfs = require('fs')
const npjoin = require('path').join
const es6Path = npjoin(__dirname, 'www')
protocol.registerSchemesAsPrivileged([{ scheme: 'es6', privileges: { standard: true, secure: true } }])
app.on('ready', async () => {
protocol.registerBufferProtocol('es6', (req, cb) => {
nfs.readFile(
npjoin(es6Path, req.url.replace('es6://', '')),
(e, b) => { cb({ mimeType: 'text/javascript', data: b }) }
)
})
await createWindow()
})
Attention! The path always seems to be transformed to lowercase
<script type="module" src="es6://path/main.js"></script>
Sorry Viziionary, not enough reputation to answer the comment.
I've now done it like this:
https://gist.github.com/jogibear9988/3349784b875c7d487bf4f43e3e071612
my problem was, I also wanted to support modules which are imported via none relative path's, so I don't need to transpile my code.
I try to load the local .html file into WebView in React Native:
// load local .html file
const PolicyHTML = require('./Policy.html');
// PolicyHTML is just a number of `1`
console.log('PolicyHTML:', PolicyHTML);
// will cause an error: JSON value '1' of type NSNumber cannot be converted to NSString
<WebView html={PolicyHTML} />
The .html file should be read as a string, not as a resource representative.
How can I load the .html file into WebView in React Native?
By the way, what is the type of those resource representatives from require()? Is it number?
try it:
const PolicyHTML = require('./Policy.html');
<WebView
source={PolicyHTML}
style={{flex: 1}}
/>
I come across this post searching for loading static html.
If your html code is retrieved using, for example, an API, you can render WebView in this way:
<WebView
originWhitelist={['*']}
source={{ html: html, baseUrl: '' }}
/>
Notice that originWhitelistis required as explained in the documentation:
Note that static html will require setting of originWhitelist for
example to ["*"].
<View style={{flex: 1}}>
<WebView
style={{flex: 1}}
source={require("./resources/index.html")}
/>
</View>
To make WebView, the parent has to has a dimension or flex:1. We could set the WebView to flex: 1 too so that it fills up the parent.
If you need to serve local assets as well, then:
put all assets together with index.html into android/app/src/main/assets/www (You can copy them there with gradle task)
Then:
var uri = Platform.OS == "android" ?
"file:///android_asset/www/index.html" :
"./web/www/index.html"
return <WebView source={{ uri }} />
** For iOS didn't tested, please add instruction, how assets should be stored
With Expo tools and generally using Expo:
import { WebView } from "react-native-webview";
import { readAsStringAsync } from "expo-file-system";
import { useAssets } from "expo-asset";
export const MusicSheet = () => {
const [index, indexLoadingError] = useAssets(
require("../assets/musicsheetview/index.html")
);
const [html, setHtml] = useState("");
if (index) {
readAsStringAsync(index[0].localUri).then((data) => {
setHtml(data);
});
}
return (
<View style={styles.container}>
<WebView
onLoad={() => {}}
source={{ html }}
onMessage={(event) => {}}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
height: 100,
display: "flex",
},
});
Try this :
Add your .html file in your project.
Write such lines of code in the file where you want to use WebView Component
const OurStoryHTML = require ('./OurStory.html')
<WebView
source={OurStoryHTML}
style={{flex: 1, marginTop : 44}}
/>
It may help you.
If you're working with assets, project directories is different on the device's directory once the project is build and you can't simply reference them via string url.
Expo
If using expo, you have to require every asset then use useAssets on the require to cache them to the local storage of the device.
useAssets will return an object that contains a localUri
(this is the uri of the image that has been cached)
you can then use the localUri and put it as the src of the image
import { useAssets } from 'expo-asset';
/* . . . */
const IndexHTML = require('./assets/index.html');
const myImage = require('./assets/splash.png');
// url link after image is cached to the device
const [imgSrc, setImgSrc] = useState('');
const [image, imerr] = useAssets(myImage);
const [html, error] = useAssets(IndexHTML);
const webViewProps = {
javaScriptEnabled: true,
androidLayerType: 'hardware',
originWhitelist: ['*'],
allowFileAccess: true,
domStorageEnabled: true,
mixedContentMode: 'always',
allowUniversalAccessFromFileURLs: true,
onLoad: () => {
console.log(image[0].localUri);
setImgSrc(image[0].localUri);
},
source: {
html: '<img src="' + imgSrc + '"/>',
},
};
return <WebView {...webViewProps} />
const webViewProps = {
...
source: IndexHTML,
};
Note: for the expo apporach, files referenced in IndexHTML will not be found
The trick is to turn your html into a string literal to utilize template strings.
Then you have to manually require each of those assets to concatenate localUrl
require() has limited types supported and you need to add a metro.config.js in your root folder.
it will give errors if you require() a .js file since it reads it as a module rather, the workaround approach would be to bundle your assets
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push(
// Adds support for asset file types
'css', 'ppt', 'obj'
);
module.exports = config;
Moreover, expo can hot reload changes done with the cached assets.
React Native
If you have the android folder in your directory, navigate to
android > app > build.gradle
then add
android {
/* link assets to the local storage of device */
sourceSets {
main { assets.srcDirs = ['src/main/assets', '../../source/assets/'] }
// [do not touch, 'relative to your asset'] }
}
. . .
finally, the relative folder you linked in gradle can be accessed through
file:///android_asset/
for ex. file:///android_asset/index.html -> /asset/index.html
return <WebView source={{uri: `file:///android_asset/index.html`}} />
For IOS, here's how
On the other hand, you have to rebuild vanilla react to see the changes in the assets.. which takes about 20 minutes or so
Alternative
A quick solution would be to integrate a static server, but this is a recent fork of the react-native-static-server that only works in vanilla react native.
Trying to clean up my Rails asset pipeline with gulp. Tired of adding every npm package scss/css file to includePaths explicitly. I've searched thoroughly and read the docs, but I'm still having trouble getting Eyeglass to find my node_modules.
Here's the setup I have so far:
var sassPaths = [
'./app/assets/stylesheets/*',
publicAssets + '/stylesheets',
'./node_modules/magic.css/1.1.0/',
'./node_modules/colors.css/sass/'
],
sassOptions = {
indentedSyntax: true,
sourceComments: true,
errLogToConsole: true,
includePaths: sassPaths,
eyeglass: {
enableImportOnce: false,
buildDir: publicAssets + '/stylesheets',
assets: {
// prefix to give assets for their output url.
httpPrefix: "assets",
installer: function(assetFile, assetUri, oldInstaller, cb) {
// oldInstaller is the standard eyeglass installer in this case.
// We proxy to it for logging purposes.
oldInstaller(assetFile, assetUri, function(err, result) {
if (err) {
console.log("Error installing '" + assetFile + "': " + err.toString());
} else {
console.log("Installed Asset '" + assetFile + "' => '" + result + "'");
}
cb(err, result);
});
},
// Add assets except for js and sass files
// The url passed to asset-url should be
// relative to the assets directory specified.
sources: [{
directory: './node_modules/',
pattern: [
'**/dist/*',
'**/dist/{scss,sass,css}/*',
'**/{scss,sass,css}/*',
'**/*.{scss, sass, css}',
'**/{scss,sass,css}/*'
],
globOpts: { ignore: ["**/*.js"], root: '.', }
}]
},
importer: function(uri, prev, done) {
done(sass.compiler.types.NULL);
}
}
},
eyeglass = new Eyeglass(sassOptions);
console.log(eyeglass.modules);
console.log(eyeglass.assets.assetPath);
stream = gulp.src(sassSource)
.pipe(sourceMaps.init())
.pipe(sass(eyeglass.options).on("error", sass.logError))
.pipe(postcss([lost(), autoprefixer({ browsers: ['last 2 versions'] }) ]));
I'm trying to point Eyeglass to the './node_modules' path and use the pattern array to have it properly parse the installed packages for css/scss to import. As I look over the docs, it doesn't really seem necessary to even have to guide it with patterns, but that it'll know where to look once you point it to the right directory.
Is there a simple way to add all my --save-dev modules to eyeglass?