简体   繁体   中英

Creating a bookmarklet using webpack, bookmarklet-loader, style and css-loader

I am trying to create a bookmarklet using bookmarklet-loader and the style-loader and css-loader. But I am having trouble importing css into my bookmarklet.

This is what I have

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
entry: {
    index: './src/index.js',
    bookmarklet: './src/bookmarklets/bookmarklet.js'
},
output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
},
target: 'web',
module: {
    rules: [
    {
        test: /\.css$/,
        use: [
            'style-loader',
            'css-loader'
        ]
    },
    {
        test: /\.js$/,
        use: [
           'bookmarklet-loader'
        ],
        include: path.join(__dirname, './src/bookmarklets')
    }
    ]
},
plugins: [
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
        title: 'Development'
    })
]

src/bookmarklets/bookmarklet.js:

import './css/style.css';

/* the rest of my bookmarklet code */

src/index.js:

import bookmarklet from './bookmarklets/bookmarklet';

var add = document.createElement("a");
add.href = "javascript:" + bookmarklet;
add.innerHTML = "Click me";

document.body.appendChild(add);

Simply adds the bookmarklet to a link on a blank page, so I can add the link to my browser.

But running webpack produces this error:

SyntaxError: Unexpected token: string (./css/style.css) at [snipped] node_modules/uglify-js/tools/node.js

I tried adding the following to my webpack.config.js:

{
    test: /\.js$/,
    use: [
       'bookmarklet-loader',
       'style-loader',
       'css-loader'
    ],
    include: path.join(__dirname, './src/bookmarklets')
}

This now compiles fine, but the bookmarklet code contains require statements so when I try and run it in the browser I get an

Uncaught ReferenceError: require is not defined

I have found this and this but have been unable to get this to work.

Edit : To explain simply the question and solution. I am trying to build a bookmarklet, but the bookmarklet-loader I am using is used for importing bookmarklets into other pieces of code. And this bookmarklet-loader in particular is not setup to handle css and templates required by the bookmarklet. I have switched to using a simple webpack config that produces a compiled javascript file and then this tool to convert that to a bookmarklet.

This is my package.json in case if its of help to anyone:

<snip>
"scripts": {
        "build": "webpack && bookmarklet dist/index.js dist/bookmarklet.js && cat dist/bookmarklet.js | xclip -selection clipboard",
}

Now npm run build builds the bookmarklet and copies it to my clipboard so I can update the bookmarklet in the browser.

I've also found this question interesting so here's an answer that would still let you use webpack for bundling your bookmarklet code.

The idea is to use a <script> tag and serve the content as a chunk through webpack:

function addScript(codeURL) {
    const scriptElement = document.createElement('script');
    scriptElement.setAttribute('src', codeURL);
    scriptElement.setAttribute('crossorigin', "anonymous");
    document.body.appendChild(scriptElement);
}

With some aditional 'magic', your index.js becomes:

const add = document.createElement("a");
add.href = "javascript:(function(){s=document.createElement('script');s.type='text/javascript';s.src='bookmarklet.bundle.js';document.body.appendChild(s);})()";
add.innerHTML = "Click me";

which is the uglified version of the above function that references your 'bookmarklet.bundle.js' chunk. (this way you don't really need the bookmarklet-loader any more)

The bookmarklet.js source (just a sample):

import './css/style.css';

let elements = require('./someOtherSource');

let list = document.createElement('ul');
for (let i = 0; i < elements.length; ++i) {
    let item = document.createElement('li');
    item.appendChild(document.createTextNode(elements[i]));
    list.appendChild(item);
}
document.body.appendChild(list);

where someOtherSource.js could be as simple as:

module.exports = [ 'a', 'b', 'c'];

and finally, your webpack.config.js becomes:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    entry: {
        index: path.resolve(__dirname, 'src/index.js'),
        bookmarklet: path.resolve(__dirname, 'src/bookmarklets/bookmarklet.js'),
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    target: 'web',
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                ]
            },
            {
                test: /\.js$/,
                use: [
                    'babel-loader',
                ],
                exclude: /node_modules/,
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'Bookmarklet',
            chunks: [ "index" ],
        })
    ]
};

Again, the advantage I see here is that you get to use your webpack bundling, css/less or whatever other loaders for building your bookmarklet. As reference also see first and second .

The solution you detail in your edit is indeed a perfectly valid way of achieving your objective.

You want to maintain a bookmarklet that depends on injecting styles.

While you can easily inject tags (like <link> and <script> ) with a bookmarklet to load external resources into the current page, it does not seem to fit your need because you do not need to make your code available on a server, and trying to link local resources on your file system might not be very reliable.

Therefore you would like the entire code and styles to be contained within the bookmarklet code. You can proceed in 2 steps:

  1. Bundle your JS code with code for inline CSS injection + CSS
  2. Encode and wrap the bundle so that its content can be used as a bookmark.

1. Bundle JS with code for inline CSS injection

This sounds like a perfect job for webpack! Indeed it is meant to bundle your code and inline your styles within the code as well, with style-loader like you did.

You could even push it slightly further by making sure any other asset (image, web font, etc.) that is potentially referred to in your CSS is also inlined, using url-loader with a limit: 0 to always inline those resources.

But as you figured out, you should not use the intermediate artefacts (like for example the output from bookmarklet-loader ), since they will likely miss some functionalities (importing style, require ).

The webpack output bundle is what you are looking for: a standalone JavaScript code that injects inline styles into the current page and executes your code.

2. Encode and wrap for bookmark

To convert the code into a bookmarklet, you have to encode the content for URI compatibility, and add an extra " javascript: " prefix.

This is the step where you have used the bookmarklet package. But in your case, since all you have is a single JavaScript file that you want to "hard code" into the bookmarklet, the wrapper is dead simple:

'javascript:' + encodeURIComponent('(function(){' + code + '})()')

You can continue using bookmarklet package or make it a very simple node script (but you should move the minification step in a previous step, typically in the webpack configuration).

Actually, it is quite easy to make a webpack plugin for this "bookmarkletify" step:

function AssetToBookmarkletPlugin() {}

AssetToBookmarkletPlugin.prototype.apply = function (compiler) {
  compiler.plugin('emit', function (compilation, callback) {
    var asset;

    // Rework each asset.
    for (var assetName in compilation.assets) {
      asset = compilation.assets[assetName];
      compilation.assets[assetName] = {
        source: function () {
          // Encode and wrap the original source to make it bookmark-ready.
          return 'javascript:' + encodeURIComponent('(function(){' + asset.source() + '})()');
        },
        size: asset.size
      }
    }

    callback();
  });
};

With these additional steps (resources inlining, CSS and JS minification, bookmarkletify assets), your webpack configuration would be:

const webpack = require('webpack');
const path = require('path');

module.exports = {
  entry: {
    index: './src/index.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  target: 'web',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: [{
        loader: 'url-loader',
        options: {limit: 0} // 0 = always inline resource
      }]
    }, {
      test: /\.css$/,
      use: ['style-loader', {
        loader: 'css-loader',
        options: {minimize: true} // Minify CSS as well
      }]
    }]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new AssetToBookmarkletPlugin()
  ]
};

The content of dist/index.js file is now ready to be copied as a bookmark.

I guess webpack bookmarklet loader is not required to create a bookmarklet itself, as the github repo suggests "bookmarklet-loader is a webpack loader that will convert any javascript file into a bookmarklet that can be used as a module throughout your application. " Not clear if thats your use case.

looking at the plugin code,

'use strict';

var uglify = require('uglify-js');

module.exports = function(source) {
  return 'module.exports = "javascript:' + encodeURIComponent(
    '(function(){'+ uglify.minify(source, { fromString: true }).code +'})();'
  ) + '"';
};

i suspect the issue could be because the only package used here is Uglifyjs which only compiles javascript, and no css loaders in the code. This plugin expects your code to be pure JS and not any CSS and HTML.

From your code i see that you have configured webpack already to build css and JS, and all this code is offering you is javascript uri pattern wrapped in a function that is URI encoded. should be pretty simple to DIY after the webpack build output. hope that helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM