Use JavaScript Proxy
to log DOM APIs
Find out which DOM APIs your code is using with Proxy
and Webpack.
import { Component, h } from 'preact';
export class App extends Component {
render({ title }) {
return (
<main style={{ textAlign: 'center' }}>
<h1>{title}</h1>
<img alt="" src={`https://i.pravatar.cc/100?u=${name}`} style={{ borderRadius: '100%' }} />
</main>
);
}
}
const isFunction = value => typeof value === 'function';
const looksLikeConstructor = key => key.search(/^[A-Z]/) !== -1;
export const addLoggingToMethod = (name, obj, key) => {
const { value: fn } = Object.getOwnPropertyDescriptor(obj, key);
if (isFunction(fn)) {
obj[key] = function() {
const args = Array.from(arguments);
console.group(`[wrapped] ${name}.${key}(`, ...args, ')');
const returnValue = fn.call(this, ...args);
console.log(returnValue);
console.groupEnd();
return returnValue;
};
}
};
export const addLoggingToMethods = (name, obj) => {
Object.keys(obj).forEach(key => {
addLoggingToMethod(name, obj, key);
});
};
export const getLoggingProxyFor = (name, target) =>
new Proxy(target, {
get: function(obj, key) {
const value = Reflect.get(obj, key);
if (name === 'window' && key === 'document') {
return document;
}
if (!isFunction(value) || looksLikeConstructor(key)) {
console.group(`[proxied] ${name}.${key}`);
console.log(value);
return value;
}
const fn = value;
return function() {
const args = Array.from(arguments);
console.group(`[proxied] ${name}.${key}(`, ...args, ')');
const returnValue = fn.call(obj, ...args);
console.log(returnValue);
console.groupEnd();
return returnValue;
};
},
set: function(obj, key, value) {
console.log(`[proxied] ${name}.${key} = ${value}`);
return Reflect.set(obj, key, value);
},
deleteProperty: function(obj, key) {
console.log(`[proxied] delete ${name}.${key}`);
return Reflect.deleteProperty(obj, key);
},
ownKeys: function(obj, key) {
console.log(`[proxied] Object.getOwnPropertyNames(${name})`);
return Reflect.ownKeys(obj, key);
},
has: function(obj, key) {
console.log(`[proxied] ${key} in ${obj}`);
return Reflect.has(obj, key);
},
defineProperty: function(obj, key, descriptor) {
console.log(`[proxied] Object.defineProperty(${name}, ${key},`, descriptor, ')');
return Reflect.defineProperty(obj, key, descriptor);
},
getOwnPropertyDescriptor: function(obj, key) {
console.log(`[proxied] Object.getOwnPropertyDescriptor(${name}, ${key})`);
return Reflect.getOwnPropertyDescriptor(obj, key);
},
});
// Add logging to methods of eg Element.prototype
export const register = () => {
Object.getOwnPropertyNames(window)
.filter(name => name.startsWith('HTML') || name.endsWith('Element'))
.concat('Element', 'Event', 'Node', 'Text')
.forEach(name => {
if (window[name] && window[name].prototype) {
addLoggingToMethods(`${name}.prototype`, window[name].prototype);
}
});
};
const { addLoggingToMethod, addLoggingToMethods } = require('.');
// Add logging to methods of eg Element.prototype
Object.getOwnPropertyNames(window)
.filter(name => name.startsWith('HTML') || name.endsWith('Element'))
.concat('Element', 'Event', 'Node', 'Text')
.forEach(name => {
if (window[name] && window[name].prototype) {
addLoggingToMethods(`${name}.prototype`, window[name].prototype);
}
});
require('./proxy/register');
const { h, Component, render } = require('preact');
const { App } = require('./components/App');
const app = <App title="What DOM does this use?" />;
render(app, document.getElementById('root'));
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>What DOM does this use?</title>
</head>
<body>
<div id="root"></div>
<script src="/dist/index.js"></script>
</body>
</html>
{
"name": "what-dom-does-preact-10-use",
"version": "0.0.0",
"dependencies": {
"preact": "10.1.1"
},
"devDependencies": {
"@babel/cli": "7.7.7",
"@babel/core": "7.7.7",
"@babel/plugin-transform-react-jsx": "7.7.7",
"@babel/preset-env": "7.7.7",
"babel-loader": "8.0.6",
"imports-loader": "0.8.0",
"webpack": "4.41.4",
"webpack-cli": "3.3.10"
},
"private": true,
"scripts": {
"build": "rm -rf dist && webpack --config webpack.config.js"
}
}
const { getLoggingProxyFor } = require('.');
module.exports = getLoggingProxyFor('document', window.document);
const path = require('path');
const webpack = require('webpack');
const srcPath = path.resolve(__dirname, 'src');
const distPath = path.resolve(__dirname, 'dist');
module.exports = {
context: srcPath,
devtool: false,
mode: 'development',
target: 'web',
entry: './index.js',
output: {
filename: './index.js',
path: distPath,
},
module: {
rules: [
{
test: filePath => !filePath.includes('/proxy/'),
use: `imports-loader?${[`document=${require.resolve('./src/proxy/document.js')}`].join('&')}`,
},
{
test: /\.m?jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { targets: { firefox: '71' } }]],
plugins: [['@babel/plugin-transform-react-jsx', { pragma: 'h', pragmaFrag: 'Fragment' }]],
},
},
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': JSON.stringify({
NODE_ENV: process.env.NODE_ENV || 'development',
}),
}),
],
};