最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Import .js file as text and use it inside WebView injectedJavaScript in react-native expo - Stack Overflow

programmeradmin3浏览0评论

I have a file content.js that includes some JavaScript code that I want to inject inside a WebView using injectedJavaScript.

I tried:

    fetch('./content.js').then((result) => {
      result = result.text();
      this.setState(previousState => (
        {contentScript: result}
      ));
    });

But it doesn't get the right file.

const contentScript = require('./content.js');

This works, but it evals the JavaScript straight away and I can't seem to find a way to convert it to string before it gets executed.

A solution is to just make copy the code of content.js into a string, but that would be pretty annoying when I want to edit the code...

Does anyone know a better solution for this?
I still have no solution to this for almost a week. :(

I have a file content.js that includes some JavaScript code that I want to inject inside a WebView using injectedJavaScript.

I tried:

    fetch('./content.js').then((result) => {
      result = result.text();
      this.setState(previousState => (
        {contentScript: result}
      ));
    });

But it doesn't get the right file.

const contentScript = require('./content.js');

This works, but it evals the JavaScript straight away and I can't seem to find a way to convert it to string before it gets executed.

A solution is to just make copy the code of content.js into a string, but that would be pretty annoying when I want to edit the code...

Does anyone know a better solution for this?
I still have no solution to this for almost a week. :(

Share Improve this question edited Jul 29, 2019 at 11:06 TheE asked Jul 23, 2019 at 18:15 TheETheE 3881 gold badge4 silver badges14 bronze badges 2
  • Add file-loaded rule to webpack and target this file – deathangel908 Commented Jul 23, 2019 at 18:21
  • @deathangel908 Could you explain me a little bit more about this? I'd also like to add that I'm using EXPO. – TheE Commented Jul 23, 2019 at 18:27
Add a ment  | 

3 Answers 3

Reset to default 3

Since expo is using webpack you can customize it. Webpack has a loaders section you might be interested in. And the loader you need is called raw-loader. So when you require some file, webpack runs this file against the chain of loaders it has in config. And by default all .js files are bundled into index.js that gets executed when you run your app. Instead you need the content of the file that raw-loader exactly does. It will insert something like

module.contentScript = function() { return "console.log('Hello world!')"}

instead of:

module.contentScript = function() { console.log('Hello World'}}

So you need:

npm install raw-loader --save-dev

and inside your code:

require('raw-loader!./content.js');

Important update re: the below

I have egg on my face, it turns out the below does not work in the default Metro bundler (I thought it was bult on top of Webpack, it isn't) and in my situation, it gave the illusion for a while it was working. The following will work if using Webpack to bundle for the web, which React Native does support, (but would be pointless for this scenario as WebView doesn't support the web anyway), or if using another bundler based on webpack with support for loaders, like the currently in development RePack (Note: this doesn't support Expo yet). I will leave my answer below for reference in case anyone using RePack or a future bundler es along.

My workaround for now involves making a custom build pipeline in my package.json to copy all my files to a build folder and then transform files with a .raw.js extension to a string and precede it with export default using a script before executing the real build on that folder.

(June 2023) An expansion of @deathangel908's answer

For those that don't want to specify the loader every time they import a given file, you can use pattern matching in your webpack.config.js file. I renamed the JS file I wanted to inject to myscript.raw.js - your editor, eslint, etc will still see it as a regular js file but you can now configure webpack to recognise any filename ending with .raw.js and use the raw loader:

(make sure you install it with npm i --save-dev raw-loader)

// webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.raw.js$/i,
        use: "raw-loader",
      },
    ],
  },
}

You can now import the file as you would with any other JS file, but it will be treated as a string:

// App.js

import myscript from "./myscript.raw.js" // myscript is just a string!

You may recieve a strange error at runtime if using the string directly: error while updating property injectedJavaScript, to counteract this, just wrap the variable in a string literal:

// App.js

<WebView
      // ...
      injectedJavaScript={`${myscript}`}
/>

Important info for TypeScript users

First, you'll want to add your .raw.js files to your typescript exclude block as these injectable files can only be pure JS and you don't want TypeScript to throw annyoing errors:

// tsconfig.json

{
    // ...
    "exclude": [
        "node_modules",
        "babel.config.js",
        "metro.config.js",
        "*.raw.js"
    ]
    // ...
}

Secondly, TypeScript will throw an error on your import: Could not find a declaration file for module 'myscript'. '/path/to/myscript.raw.js' implicitly has an 'any' type To fix this you must create a declarations (.d.ts) file somewhere in your source, I just made a file in the root of my repository called declarations.d.ts:

// declarations.d.ts

declare module "*.raw.js" {
  const contents: string
  export default contents
}

UPDATE: I ran into some issues and switched to using Rollup instead of Webpack. I now have 2 rollup config files for each platform.

I created a separate project and then used npm install project-scripts to achieve that. In the new npm project, I used webpack and a custom webpack plugin which reads the contents of all files in src/.js and src/.d.ts and exports them to dist/index.js and dist/index.d.ts.

Here's the plugin.

const path = require('path');

class FileContentsPlugin {
    constructor(options) {
        this.options = options;
    }

    apply(piler) {
        piler.hooks.emit.tapAsync('FileContentsPlugin', (pilation, callback) => {
            let content = '';
            for (let fileName of Object.keys(pilation.assets)) {
                if (fileName.endsWith('.js')) {
                    content += `export const ${path.parse(fileName).name} = \`${pilation.assets[fileName]._value}\`;\n`;
                }
            }
            pilation.assets['index.js'] = {
                source: () => content,
                size: () => content.length,
            };

            callback();
        });
    }
}

module.exports = FileContentsPlugin;

And webpack.config.js

const path = require('path');
const glob = require('glob');
const FileContentsPlugin = require('./file-contents-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const entryFiles = glob
    .sync('./src/*.js')
    .reduce((entryObj, currentValue) => {
        const parsedPath = path.parse(path.relative('./src', currentValue));
        const entryValue = `${parsedPath.dir}${path.sep}${parsedPath.name}`;
        return {
            ...entryObj,
            [entryValue]: currentValue,
        };
    }, {});

module.exports = {
    mode: process.env.NODE_ENV || 'development',
    entry: entryFiles,
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                test: /\.m?js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            ['@babel/preset-env', { targets: "defaults" }]
                        ],
                        plugins: ['@babel/plugin-transform-runtime']
                    }
                },
            },
        ],
    },
    plugins: [
        new CopyWebpackPlugin({
            patterns: [
                { from: './package.json', to: '.' },
                { from: '**/*.d.ts', to: '.', context: path.resolve(__dirname, './src'), },
            ],
        }),
        new FileContentsPlugin(),
    ],
};

Now run npx webpack to generate dist/ files.

Note: React Native and/or Expo don't work with npm linked packages so I had to add it to package.json and then use the cp mand manually. Once your project is in production, you won't need to do that.

cd current-project
cp -r ../project-scripts/dist/* node_modules/project-scripts

Now you can import your scripts as strings for webview.

import { Script1 } from 'project-scripts'

<WebView
    source={{ uri: 'https://google.' }}
    javaScriptEnabled={true}
    injectedJavaScript={Script1}
    onMessage={console.log}
></WebView>

In production, you need to use npmjs. or verdaccio if you want local and private experience.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论