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

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:
Bundle your JS code with code for inline CSS injection + CSS
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!

Related

Bundle Wasm + JS file into one using webpack

I am currently using Emscripten to compile our C++ Code into Wasm. By doing so I output two files *.js and *.wasm. Later I use our implementation to write more Javascript code on top of that which leads us to 3 files:
index.js
wasmFile.js
wasmFile.wasm
I am trying to use webpack to create a single file that will package everything at build time rather than runtime with this piece of code:
function loadScript(url = "wasmFile.js") {
var script = document.createElement( "script" );
script.src = url;
document.getElementsByTagName( "head" )[0].appendChild( script );
await new Promise<void>((res) => {
Module.onRuntimeInitialized = () => res();
});
}
I have looked into https://github.com/ballercat/wasm-loader However, it looks like i would need to create a WebAssembly.Instance for all my function - and the Wasm file has a lot of functions to create an instance for each.
This is how our WebPack config looks like at the moment:
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.ts$/,
enforce: 'pre',
loader: 'tslint-loader',
options: {
emitErrors: true
}
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist')
}
};
Is there something we are missing on this? Or another package i could use to accomplish this? Any help would be wonderful.
Thanks!
You can build your app as a single JS file using -s SINGLE_FILE=1
Note: This answer is probably not exactly what you want but it solved similar problem for me.
Try out the --bind option. It will output a js and wasm file, the js file loads wasm files and exports the functions to be used in js.
Embind doc
Emcc doc search for bind

Webpage does not refresh in production until hard refresh

For my website, I have a flask server serving a file generated by webpack. Unfortunately, when I update the file, the webpage often does not update until a hard refresh (Ctrl-F5), due to browser caching. I want the webpage to update after a regular refresh, as most users do not know about hard refresh. In development, there are ways of getting around hard refresh, such as webpack-dev-server. What is the easiest way of doing this in production?
I have the following webpack.config.js file:
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: ['react-hot-loader/patch', './js/main.js'],
output: {
filename: "./static/bundle.js",
},
resolveLoader: {
moduleExtensions: ['-loader']
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loaders: 'babel',
query: {
presets: ['react', 'es2015', 'stage-0']
}
},
{
test: /\.css$/,
loader: 'style-loader',
},
{
test: /\.css$/,
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
]
}
};
The Flask server is serving an index.html file that looks like this:
<html>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
You need to bust the cache yourself. Webpack has the provision for it.
output: {
filename: "./static/bundle-[hash:6].js",
},
The bundled file generated would look like: bundle-1e3dab.js
Now, in HTML:
<html>
<body>
<div id="app"></div>
<script src="bundle-1e3dab.js"></script>
</body>
</html>
Now every time you build, if something gets changed, the hash will be updated.
NOTE: Don't forget to update the file path in HTML each time you build or customize your build to have a replace task which automatically updates the HTML file. You can use Webpack Manifest Plugin for this.
UPDATE
Change entry in your webpack file:
// Entry, files to be bundled separately
entry: {
'main': [
'react-hot-loader/patch',
'./js/main.js'
]
},
And for updating the hash
var fs = require('fs');
var path = require('path');
var basePath = path.join(__dirname, '/');
function replace (statsData, fileName, readFilePath, regex, assetChunkName, writeFilePath, replaceWith) {
// Read the data so that hash can be read
let stats = statsData.toJson();
if (!stats.errors.length) {
// read the file i.e. index.html and store the contents
let contents = fs.readFileSync(path.join(readFilePath, fileName), 'utf8'),
// Replace the pattern with the user-defined replacedWith variable or the chunkHash webpack provides
htmlOutput = contents.replace(
regex,
replaceWith || stats.assetsByChunkName[assetChunkName][0]
);
// Write back the modified contents into the file
fs.writeFileSync(path.join(writeFilePath, fileName), htmlOutput);
}
}
inside the configuration, after module key, add the following code:
plugins: [
function() {
// To be executed when build is done
this.plugin('done', (statsData) => {
// `statsData` has the info regarding the file bundling(hash)
// Replace the filename with the update chunkHash for build/prod only
replace(
statsData,
'index.html', // filename which needs to be modified
path.join(basePath, '/dist/'), // path from where to read index.html
/bundle\.js/i, // regex i.e. which needs to be replaced
'bundle',
path.join(basePath, '/dist/')) // path from where to write index.html, can be same if needs ot override
})
}
]
Replace the pathname and you're done :)
let me know if you face any errors.
This is not a question about webpack, a server has no way to force browser don't use cache. What you can do is adding some postfix to your url like replacing bundle.js with bundle.js<it's md5>. In this case, as the url is different, browser will treat it as a new resource.

Webpack: load js modules dynamically from a URL at runtime

I am currently working on a library which should dynamically load a JavaScript from a remote host and instantiate that.
I am writing the library in TypeScript and my plan is to use Webpack as a bundler.
On another host (remote system) runs a provider which should serve JavaScript code (see here: https://stubs.d-koppenhagen.de/stubs/SimpleStub.js).
The library will dynamically resolve "Identitys" via Webfinger. These Identitys represented by an object and they having a property pointing to a "Stub Provider" which will serve JavaScript code (the link I mentioned before). My library should load this script during runtime (s the library don't know the target for this stubs before) and should use it.
currently my webpack.config.js looks like the following:
var path = require('path');
var webpack = require('webpack');
var WebpackBuildNotifierPlugin = require('webpack-build-notifier');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PATHS = {
src: path.join(__dirname, './src'),
build: path.join(__dirname, './dist')
};
module.exports = {
entry: {
wonder: PATHS.src + '/wonder'
},
output: {
path: PATHS.build,
filename: '[name].js',
library: 'Wonder',
libraryTarget: 'umd'
},
devtool: 'source-map',
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts-loader'
}
]
},
resolve: {
extensions: ['.ts', '.js']
},
plugins: [
new WebpackBuildNotifierPlugin()
]
};
And here is a part of the library code:
require.ensure([], function() {
require(localMsgStubUrl);
});
When I am now including the bundled library in an example app, I will get the following error:
Error: loading chunk failed
So is there a way to tell webpack not to bundle the code which is required from a external resource loaded from a URL so that I can use that code like it is?
I don't want to tell webpack the URL statically in a config as maybe other stubs I am loading are located on a different target.
Thanks in advance for your help!

Using CSS in Webpack

I've inherited a web app that uses webpack. In my app, I have a directory called "pub", which looks like this:
./pub
/styles
app.css
/images
brand.png
I have been trying unsuccessfully all morning to use these via webpack. In my webpack.config.js file, I have the following:
const path = require('path');
const projectRoot = path.resolve(__dirname, '../');
module.exports = {
entry: {
app: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
}
]
}
};
Then, in my index.js file, I have the following:
import logoImage from './public/images/brand.png';
require("css!./public/css/app.css");
When I run webpack, I receive an error that says:
BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.
You need to specify 'css-loader' instead of 'css',
see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed
I don't really understand this error. When I look at it, and then I look at my webpack.config.js file, it looks to me like I'm using css-loader. Beyond that though, how do I use a style in my webpage once the require statement is working. I'm just trying to use webpack with a web app and want to import my brand and CSS and I can't figure it out.
You don't need the css! in your require statement
require("css!./public/css/app.css");
You can just use
require("./public/css/app.css");
Because you are testing files with:
{
test: /\.css$/, // <-- here
loader: "style-loader!css-loader"
},
Or without the rule in your webpack config
// No test in rules matched but you tell webpack
// explicitly to use the css loader
require("style-loader!css-loader!./public/css/app.css");
Your hierarchy is pub/styles/app.css but the location you use in your require is public/css/app.css. It looks like you're trying to call your css from the wrong location.
If this doesn't solve your issue, check out this link https://webpack.github.io/docs/stylesheets.html
The first step on that page is to install css-loader and configure it, this might be a good place to start.

Concat and minify all less files with Webpack without importing them

I have a folder of around 20 separate less files that I need to concatenate into a single file via Webpack and store this in my /dist folder. My current Webpack config file is as follows:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const bundleOutputDir = './wwwroot/dist';
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
return [{
stats: { modules: false },
entry: { 'main': './ClientApp/boot.ts' },
resolve: { extensions: ['.js', '.ts'] },
output: {
path: path.join(__dirname, bundleOutputDir),
filename: '[name].js',
publicPath: '/dist/'
},
module: {
rules: [
{ test: /\.ts$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.html$/, use: 'raw-loader' },
{ test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : ExtractTextPlugin.extract({ use: 'css-loader' }) },
{ test: /\.less/, use: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [
new CheckerPlugin(),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(bundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin('site.less'),
new ExtractTextPlugin('site.css')
])
}];
};
If I try and import each single .less file into the boot.ts entry file, I get a less error stating that the less variables that I've declared are not being recognised, which is how I came to the conclusion that I need to concat these files beforehand. I come from a gulp background, so any help to get me up and running with this would be greatly appreciated.
If there is an alternative way to get all less compiled to css and working correctly, without the need for concat, then I'm open to suggestions.
Webpack is a module bundler and uses the module syntax for JavaScript (ES6, CommonJS, AMD..), CSS (#import, url) and even HTML (through src attribute) to build the app's dependency graph and then serialize it in several bundles.
In your case, when you import the *.less files the errors are because you miss CSS modules. In other words, on the places where you have used variables defined in other file, that file was not #import-ed.
With Webpack it's recommended to modularize everything, therefore I would recommend to add the missing CSS modules. I had the same issue when I was migrating a project from Grunt to Webpack. Other temporary solution is to create an index.less file where you will #import all the less files (note: the order is important) and then import that file in app's entry file (ex. boot.ts).

Categories

Resources