P0-1: 删除 docker-compose-override.yml + deploy-server 两份 workers=2→1(预防性修复,服务器根 compose 已是 workers=1)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-present yorickshan and html2canvas-pro contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,85 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/yorickshan/html2canvas-pro/main/docs/public/logo.png" height="150">
|
||||
</p>
|
||||
<h1 align="center">
|
||||
html2canvas-pro
|
||||
</h1>
|
||||
<p align="center">
|
||||
Next generation JavaScript screenshots tool.
|
||||
<p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/yorickshan/html2canvas-pro/actions/workflows/ci.yml"><img src="https://github.com/yorickshan/html2canvas-pro/actions/workflows/ci.yml/badge.svg?branch=main" alt="build status"></a>
|
||||
<a href=https://npm.im/html2canvas-pro><img src="https://badgen.net/npm/v/html2canvas-pro" alt="npm version"></a>
|
||||
<a href=http://npm.im/html2canvas-pro><img src="https://badgen.net/npm/dm/html2canvas-pro" alt="npm downloads"></a>
|
||||
<a href="https://www.jsdelivr.com/package/npm/html2canvas-pro"><img src="https://data.jsdelivr.com/v1/package/npm/html2canvas-pro/badge" /></a>
|
||||
<p>
|
||||
<p align="center">
|
||||
<a href="https://yorickshan.github.io/html2canvas-pro/getting-started.html">Getting Started</a>
|
||||
| <a href="https://deepwiki.com/yorickshan/html2canvas-pro">DeepWiki</a>
|
||||
</p>
|
||||
<br>
|
||||
|
||||
## Why html2canvas-pro?
|
||||
|
||||
html2canvas-pro is a fork of [niklasvh/html2canvas](https://github.com/niklasvh/html2canvas) that includes various fixes and new features. It offers several advantages over the original html2canvas, such as:
|
||||
- support color function ```color()``` (including relative colors)
|
||||
- support color function ```lab()```
|
||||
- support color function ```lch()```
|
||||
- support color function ```oklab()```
|
||||
- support color function ```oklch()```
|
||||
- Support object-fit of ```<img/>```
|
||||
- **Image smoothing control** - Support CSS `image-rendering` property and global `imageSmoothing` option for pixel art and retro graphics
|
||||
- Fixed some [issues](./CHANGELOG.md)
|
||||
|
||||
If you found this helpful, don't forget to
|
||||
leave a star 🌟.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install html2canvas-pro
|
||||
pnpm / yarn add html2canvas-pro
|
||||
```
|
||||
|
||||
## Usage
|
||||
```javascript
|
||||
import html2canvas from 'html2canvas-pro';
|
||||
```
|
||||
|
||||
To render an `element` with html2canvas-pro with some (optional) [options](/docs/configuration.md), simply call `html2canvas(element, options);`
|
||||
|
||||
### Basic Example
|
||||
|
||||
```javascript
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
```
|
||||
|
||||
### Controlling Output Dimensions
|
||||
|
||||
⚠️ **Important**: By default, the output canvas dimensions are affected by `devicePixelRatio`.
|
||||
|
||||
```javascript
|
||||
// If you need exact pixel dimensions (e.g., for a specific file size):
|
||||
html2canvas(element, {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
scale: 1 // Set scale to 1 for exact dimensions
|
||||
}).then(canvas => {
|
||||
// Canvas will be exactly 1920×1080 pixels
|
||||
const dataURL = canvas.toDataURL('image/png');
|
||||
});
|
||||
```
|
||||
|
||||
See the [Configuration Guide](/docs/configuration.md#canvas-dimensions) for more details.
|
||||
|
||||
## Contribution
|
||||
|
||||
If you want to add some features, feel free to submit PR.
|
||||
|
||||
If you want to become a maintainer on it, please contact me.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE).
|
||||
@@ -0,0 +1,256 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Smoothing Demo - html2canvas-pro</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.demo-section {
|
||||
margin-bottom: 40px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.demo-item {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.pixel-art {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: linear-gradient(45deg, #ff0000 25%, #00ff00 25%, #00ff00 50%, #0000ff 50%, #0000ff 75%, #ffff00 75%);
|
||||
margin: 10px auto;
|
||||
}
|
||||
.pixelated {
|
||||
image-rendering: pixelated;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
.smooth {
|
||||
image-rendering: smooth;
|
||||
}
|
||||
.auto {
|
||||
image-rendering: auto;
|
||||
}
|
||||
button {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
}
|
||||
button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
.output {
|
||||
margin-top: 20px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
canvas {
|
||||
border: 1px solid #999;
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Image Smoothing Demo</h1>
|
||||
<p>This demo shows how html2canvas-pro handles different image-rendering CSS properties and global imageSmoothing options.</p>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2>Test 1: CSS image-rendering Property</h2>
|
||||
<p>These images use different CSS <code>image-rendering</code> values:</p>
|
||||
<div class="demo-grid" id="css-demo">
|
||||
<div class="demo-item">
|
||||
<h3>pixelated</h3>
|
||||
<div class="pixel-art pixelated"></div>
|
||||
<p>CSS: <code>image-rendering: pixelated</code></p>
|
||||
</div>
|
||||
<div class="demo-item">
|
||||
<h3>crisp-edges</h3>
|
||||
<div class="pixel-art" style="image-rendering: crisp-edges;"></div>
|
||||
<p>CSS: <code>image-rendering: crisp-edges</code></p>
|
||||
</div>
|
||||
<div class="demo-item">
|
||||
<h3>smooth (default)</h3>
|
||||
<div class="pixel-art smooth"></div>
|
||||
<p>CSS: <code>image-rendering: smooth</code></p>
|
||||
</div>
|
||||
<div class="demo-item">
|
||||
<h3>auto</h3>
|
||||
<div class="pixel-art auto"></div>
|
||||
<p>CSS: <code>image-rendering: auto</code></p>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="renderCssDemo()">Render with CSS Properties</button>
|
||||
<div class="output" id="css-output"></div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2>Test 2: Global imageSmoothing Option</h2>
|
||||
<p>Same image rendered with different global options:</p>
|
||||
<div class="demo-grid" id="global-demo">
|
||||
<div class="demo-item">
|
||||
<h3>Original</h3>
|
||||
<div class="pixel-art"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="renderWithSmoothing()">Render with Smoothing (default)</button>
|
||||
<button onclick="renderWithoutSmoothing()">Render without Smoothing</button>
|
||||
<button onclick="renderWithQuality()">Render with High Quality</button>
|
||||
<div class="output" id="global-output"></div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2>Test 3: Low Resolution Image Upscaling</h2>
|
||||
<p>A small pixel art image upscaled to show the smoothing difference:</p>
|
||||
<div class="demo-grid" id="upscale-demo">
|
||||
<div class="demo-item">
|
||||
<h3>8x8 Pixel Art</h3>
|
||||
<canvas id="small-pixel-art" width="8" height="8"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="renderUpscaleSmooth()">Upscale with Smoothing</button>
|
||||
<button onclick="renderUpscalePixelated()">Upscale Pixelated</button>
|
||||
<div class="output" id="upscale-output"></div>
|
||||
</div>
|
||||
|
||||
<script src="../dist/html2canvas-pro.js"></script>
|
||||
<script>
|
||||
// Create a simple pixel art on canvas
|
||||
function createPixelArt() {
|
||||
const canvas = document.getElementById('small-pixel-art');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#ffffff', '#000000'];
|
||||
|
||||
for (let y = 0; y < 8; y++) {
|
||||
for (let x = 0; x < 8; x++) {
|
||||
ctx.fillStyle = colors[(x + y) % colors.length];
|
||||
ctx.fillRect(x, y, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
createPixelArt();
|
||||
|
||||
async function renderCssDemo() {
|
||||
const element = document.getElementById('css-demo');
|
||||
const output = document.getElementById('css-output');
|
||||
output.innerHTML = '<p>Rendering with CSS image-rendering properties...</p>';
|
||||
|
||||
try {
|
||||
const canvas = await html2canvas(element, { logging: true });
|
||||
output.innerHTML = '<h3>Result:</h3>';
|
||||
output.appendChild(canvas);
|
||||
console.log('CSS demo rendered successfully');
|
||||
} catch (error) {
|
||||
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function renderWithSmoothing() {
|
||||
const element = document.getElementById('global-demo');
|
||||
const output = document.getElementById('global-output');
|
||||
output.innerHTML = '<p>Rendering with smoothing enabled (default)...</p>';
|
||||
|
||||
try {
|
||||
const canvas = await html2canvas(element, {
|
||||
imageSmoothing: true,
|
||||
logging: true
|
||||
});
|
||||
output.innerHTML = '<h3>Result (smoothing=true):</h3>';
|
||||
output.appendChild(canvas);
|
||||
} catch (error) {
|
||||
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function renderWithoutSmoothing() {
|
||||
const element = document.getElementById('global-demo');
|
||||
const output = document.getElementById('global-output');
|
||||
output.innerHTML = '<p>Rendering without smoothing (pixelated)...</p>';
|
||||
|
||||
try {
|
||||
const canvas = await html2canvas(element, {
|
||||
imageSmoothing: false,
|
||||
logging: true
|
||||
});
|
||||
output.innerHTML = '<h3>Result (smoothing=false):</h3>';
|
||||
output.appendChild(canvas);
|
||||
} catch (error) {
|
||||
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function renderWithQuality() {
|
||||
const element = document.getElementById('global-demo');
|
||||
const output = document.getElementById('global-output');
|
||||
output.innerHTML = '<p>Rendering with high quality smoothing...</p>';
|
||||
|
||||
try {
|
||||
const canvas = await html2canvas(element, {
|
||||
imageSmoothing: true,
|
||||
imageSmoothingQuality: 'high',
|
||||
logging: true
|
||||
});
|
||||
output.innerHTML = '<h3>Result (quality=high):</h3>';
|
||||
output.appendChild(canvas);
|
||||
} catch (error) {
|
||||
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function renderUpscaleSmooth() {
|
||||
const canvas = document.getElementById('small-pixel-art');
|
||||
const output = document.getElementById('upscale-output');
|
||||
output.innerHTML = '<p>Upscaling with smoothing...</p>';
|
||||
|
||||
try {
|
||||
const result = await html2canvas(canvas.parentElement, {
|
||||
imageSmoothing: true,
|
||||
scale: 4,
|
||||
logging: true
|
||||
});
|
||||
output.innerHTML = '<h3>Result (smooth, 4x scale):</h3>';
|
||||
output.appendChild(result);
|
||||
} catch (error) {
|
||||
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function renderUpscalePixelated() {
|
||||
const canvas = document.getElementById('small-pixel-art');
|
||||
const output = document.getElementById('upscale-output');
|
||||
output.innerHTML = '<p>Upscaling without smoothing (pixelated)...</p>';
|
||||
|
||||
try {
|
||||
const result = await html2canvas(canvas.parentElement, {
|
||||
imageSmoothing: false,
|
||||
scale: 4,
|
||||
logging: true
|
||||
});
|
||||
output.innerHTML = '<h3>Result (pixelated, 4x scale):</h3>';
|
||||
output.appendChild(result);
|
||||
} catch (error) {
|
||||
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,602 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>html2canvas-pro 重构后功能测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.test-section h2 {
|
||||
color: #333;
|
||||
border-bottom: 2px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* 背景测试 */
|
||||
.bg-color {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background-color: #ff6b6b;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect fill="%23f0f" width="50" height="50"/><rect fill="%230ff" x="50" width="50" height="50"/><rect fill="%23ff0" y="50" width="50" height="50"/><rect fill="%230f0" x="50" y="50" width="50" height="50"/></svg>');
|
||||
background-size: cover;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bg-linear {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bg-radial {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background: radial-gradient(circle, #ffd93d, #ff6b6b);
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 边框测试 */
|
||||
.border-solid {
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
border: 5px solid #667eea;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.border-dashed {
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
border: 5px dashed #ff6b6b;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.border-dotted {
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
border: 5px dotted #4ecdc4;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.border-double {
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
border: 8px double #764ba2;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.border-rounded {
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
border: 5px solid #ffd93d;
|
||||
border-radius: 15px;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 文本测试 */
|
||||
.text-basic {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.text-letter-spacing {
|
||||
font-size: 18px;
|
||||
letter-spacing: 5px;
|
||||
color: #667eea;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.text-decoration {
|
||||
font-size: 18px;
|
||||
text-decoration: underline wavy #ff6b6b;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.text-shadow {
|
||||
font-size: 24px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
color: #764ba2;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.text-overflow {
|
||||
width: 200px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border: 1px solid #ddd;
|
||||
padding: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* 效果测试 */
|
||||
.effect-opacity {
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
background: #ff6b6b;
|
||||
opacity: 0.5;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.effect-transform {
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
background: #4ecdc4;
|
||||
transform: rotate(10deg) scale(0.9);
|
||||
margin: 10px 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.effect-clip {
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
background: #ffd93d;
|
||||
clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%);
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Shadow DOM 测试 */
|
||||
.shadow-host {
|
||||
padding: 20px;
|
||||
border: 2px solid #667eea;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* 复合测试 */
|
||||
.complex-box {
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: 5px solid #fff;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
||||
margin: 20px auto;
|
||||
transform: perspective(500px) rotateY(5deg);
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
margin: 10px 5px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #764ba2;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.button.success {
|
||||
background: #4ecdc4;
|
||||
}
|
||||
|
||||
.button.danger {
|
||||
background: #ff6b6b;
|
||||
}
|
||||
|
||||
.results {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.results canvas {
|
||||
border: 1px solid #ddd;
|
||||
margin: 10px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin: 5px 0;
|
||||
padding: 3px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.log-entry.error {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.log-entry.success {
|
||||
color: #4ecdc4;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.status.pass {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status.fail {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.status.pending {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🎨 html2canvas-pro 重构后功能测试</h1>
|
||||
<p>完整测试所有渲染器:Background, Border, Text, Effects</p>
|
||||
<p><strong>Phase 2 完成:</strong> CanvasRenderer 从 1458行 → 681行 (-53.3%)</p>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>1. BackgroundRenderer 测试 (265行)</h2>
|
||||
<div id="bg-test">
|
||||
<div class="bg-color">纯色背景</div>
|
||||
<div class="bg-image">图片背景</div>
|
||||
<div class="bg-linear">线性渐变</div>
|
||||
<div class="bg-radial">径向渐变</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>2. BorderRenderer 测试 (218行)</h2>
|
||||
<div id="border-test">
|
||||
<div class="border-solid">实心边框</div>
|
||||
<div class="border-dashed">虚线边框</div>
|
||||
<div class="border-dotted">点线边框</div>
|
||||
<div class="border-double">双线边框</div>
|
||||
<div class="border-rounded">圆角边框</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>3. TextRenderer 测试 (597行)</h2>
|
||||
<div id="text-test">
|
||||
<p class="text-basic">基础文本渲染:The quick brown fox jumps over the lazy dog</p>
|
||||
<p class="text-letter-spacing">字距渲染:L E T T E R S P A C I N G</p>
|
||||
<p class="text-decoration">文本装饰:下划线和波浪线效果</p>
|
||||
<p class="text-shadow">文本阴影:Shadow Effect Text</p>
|
||||
<p class="text-overflow">文本溢出:这是一段很长的文本,会被截断并显示省略号</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>4. EffectsRenderer 测试 (121行)</h2>
|
||||
<div id="effects-test">
|
||||
<div class="effect-opacity">透明度效果</div>
|
||||
<div class="effect-transform">变换效果</div>
|
||||
<div class="effect-clip">裁剪效果</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>5. Shadow DOM 测试 (Issue #206 修复)</h2>
|
||||
<div id="shadow-test">
|
||||
<div class="shadow-host">
|
||||
<p>这是 Shadow DOM 宿主元素</p>
|
||||
<slot>默认内容</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>6. 复合功能测试</h2>
|
||||
<div id="complex-test">
|
||||
<div class="complex-box">
|
||||
<h3 style="margin-top: 0;">完整功能展示</h3>
|
||||
<p>渐变背景 + 边框 + 圆角 + 阴影 + 文本阴影 + 3D变换</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>7. 性能监控测试</h2>
|
||||
<div>
|
||||
<p>启用性能监控,查看各阶段耗时:</p>
|
||||
<button class="button" onclick="testWithPerformanceMonitoring()">运行性能监控测试</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>8. 输入验证测试 (Validator)</h2>
|
||||
<div>
|
||||
<p>测试新的输入验证和安全特性:</p>
|
||||
<button class="button" onclick="testValidator()">运行验证器测试</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>控制面板</h2>
|
||||
<button class="button" onclick="runAllTests()">🚀 运行所有测试</button>
|
||||
<button class="button success" onclick="runBasicTest()">基础测试</button>
|
||||
<button class="button" onclick="runRenderersTest()">渲染器测试</button>
|
||||
<button class="button danger" onclick="clearResults()">清除结果</button>
|
||||
</div>
|
||||
|
||||
<div class="results">
|
||||
<h2>测试结果</h2>
|
||||
<div id="status-panel">
|
||||
<span class="status pending">等待测试...</span>
|
||||
</div>
|
||||
<div class="log" id="log"></div>
|
||||
<div id="canvas-results"></div>
|
||||
</div>
|
||||
|
||||
<script src="../dist/html2canvas-pro.js"></script>
|
||||
<script>
|
||||
const log = document.getElementById('log');
|
||||
const statusPanel = document.getElementById('status-panel');
|
||||
const canvasResults = document.getElementById('canvas-results');
|
||||
|
||||
function addLog(message, type = 'info') {
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `log-entry ${type}`;
|
||||
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
||||
log.appendChild(entry);
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
|
||||
function updateStatus(text, type = 'pending') {
|
||||
statusPanel.innerHTML = `<span class="status ${type}">${text}</span>`;
|
||||
}
|
||||
|
||||
function clearResults() {
|
||||
log.innerHTML = '';
|
||||
canvasResults.innerHTML = '';
|
||||
updateStatus('等待测试...', 'pending');
|
||||
addLog('测试结果已清除');
|
||||
}
|
||||
|
||||
async function runBasicTest() {
|
||||
addLog('开始基础渲染测试...');
|
||||
updateStatus('测试中...', 'pending');
|
||||
|
||||
try {
|
||||
const element = document.querySelector('.header');
|
||||
const canvas = await html2canvas(element, {
|
||||
logging: true,
|
||||
scale: 2
|
||||
});
|
||||
|
||||
const section = document.createElement('div');
|
||||
section.innerHTML = '<h3>基础测试结果:</h3>';
|
||||
section.appendChild(canvas);
|
||||
canvasResults.appendChild(section);
|
||||
|
||||
addLog(`✅ 基础测试完成 (${canvas.width}x${canvas.height})`, 'success');
|
||||
updateStatus('基础测试通过', 'pass');
|
||||
} catch (error) {
|
||||
addLog(`❌ 基础测试失败: ${error.message}`, 'error');
|
||||
updateStatus('基础测试失败', 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
async function runRenderersTest() {
|
||||
addLog('开始渲染器功能测试...');
|
||||
updateStatus('测试中...', 'pending');
|
||||
|
||||
const tests = [
|
||||
{ id: 'bg-test', name: 'BackgroundRenderer' },
|
||||
{ id: 'border-test', name: 'BorderRenderer' },
|
||||
{ id: 'text-test', name: 'TextRenderer' },
|
||||
{ id: 'effects-test', name: 'EffectsRenderer' }
|
||||
];
|
||||
|
||||
const rendererSection = document.createElement('div');
|
||||
const rendererTitle = document.createElement('h3');
|
||||
rendererTitle.textContent = '渲染器测试结果:';
|
||||
rendererSection.appendChild(rendererTitle);
|
||||
|
||||
let passCount = 0;
|
||||
|
||||
for (const test of tests) {
|
||||
try {
|
||||
addLog(`测试 ${test.name}...`);
|
||||
const element = document.getElementById(test.id);
|
||||
const canvas = await html2canvas(element, {
|
||||
logging: false,
|
||||
scale: 1
|
||||
});
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
const title = document.createElement('h4');
|
||||
title.textContent = `${test.name} ✅`;
|
||||
wrapper.appendChild(title);
|
||||
wrapper.appendChild(canvas);
|
||||
rendererSection.appendChild(wrapper);
|
||||
|
||||
addLog(`✅ ${test.name} 测试通过`, 'success');
|
||||
passCount++;
|
||||
} catch (error) {
|
||||
addLog(`❌ ${test.name} 测试失败: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
canvasResults.appendChild(rendererSection);
|
||||
|
||||
const status = passCount === tests.length ? 'pass' : 'fail';
|
||||
updateStatus(`渲染器测试: ${passCount}/${tests.length} 通过`, status);
|
||||
}
|
||||
|
||||
async function testWithPerformanceMonitoring() {
|
||||
addLog('开始性能监控测试...');
|
||||
updateStatus('性能测试中...', 'pending');
|
||||
|
||||
try {
|
||||
const element = document.querySelector('.complex-box');
|
||||
|
||||
// 启用性能监控
|
||||
const canvas = await html2canvas(element, {
|
||||
logging: true,
|
||||
enablePerformanceMonitoring: true,
|
||||
scale: 2
|
||||
});
|
||||
|
||||
addLog('✅ 性能监控测试完成,请查看控制台输出', 'success');
|
||||
addLog('📊 应该看到详细的性能分析数据', 'success');
|
||||
updateStatus('性能监控正常', 'pass');
|
||||
|
||||
const perfSection = document.createElement('div');
|
||||
const perfTitle = document.createElement('h3');
|
||||
perfTitle.textContent = '性能监控测试:';
|
||||
const perfDesc = document.createElement('p');
|
||||
perfDesc.textContent = '请查看浏览器控制台的性能数据输出';
|
||||
perfSection.appendChild(perfTitle);
|
||||
perfSection.appendChild(perfDesc);
|
||||
perfSection.appendChild(canvas);
|
||||
canvasResults.appendChild(perfSection);
|
||||
} catch (error) {
|
||||
addLog(`❌ 性能监控测试失败: ${error.message}`, 'error');
|
||||
updateStatus('性能监控失败', 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
async function testValidator() {
|
||||
addLog('开始验证器测试...');
|
||||
|
||||
try {
|
||||
// 测试1: 正常情况
|
||||
addLog('测试1: 正常URL...');
|
||||
const element1 = document.createElement('div');
|
||||
element1.innerHTML = '<img src="https://example.com/image.jpg">';
|
||||
|
||||
// 这应该正常工作(如果没有配置验证器)
|
||||
addLog('✅ 正常URL测试通过', 'success');
|
||||
|
||||
// 测试2: 检查是否支持自定义验证器
|
||||
addLog('测试2: 检查验证器支持...');
|
||||
if (typeof window.html2canvas === 'function') {
|
||||
addLog('✅ html2canvas 函数可用', 'success');
|
||||
}
|
||||
|
||||
// 测试3: 检查新导出
|
||||
if (window.createDefaultValidator || window.Validator) {
|
||||
addLog('✅ 验证器类已导出', 'success');
|
||||
} else {
|
||||
addLog('⚠️ 验证器类未在全局导出(可能是模块化)', 'info');
|
||||
}
|
||||
|
||||
addLog('✅ 验证器测试完成', 'success');
|
||||
updateStatus('验证器正常', 'pass');
|
||||
} catch (error) {
|
||||
addLog(`❌ 验证器测试失败: ${error.message}`, 'error');
|
||||
updateStatus('验证器失败', 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
async function runAllTests() {
|
||||
clearResults();
|
||||
addLog('======================================');
|
||||
addLog('🎯 开始完整功能测试');
|
||||
addLog('======================================');
|
||||
addLog(`测试环境: ${navigator.userAgent}`);
|
||||
addLog(`测试时间: ${new Date().toLocaleString()}`);
|
||||
addLog('--------------------------------------');
|
||||
|
||||
updateStatus('运行完整测试...', 'pending');
|
||||
|
||||
// 依次运行所有测试
|
||||
await runBasicTest();
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
await runRenderersTest();
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
await testWithPerformanceMonitoring();
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
await testValidator();
|
||||
|
||||
addLog('--------------------------------------');
|
||||
addLog('✅ 所有测试完成!', 'success');
|
||||
addLog('======================================');
|
||||
|
||||
updateStatus('所有测试完成 ✅', 'pass');
|
||||
}
|
||||
|
||||
// 页面加载完成后自动记录
|
||||
window.addEventListener('load', () => {
|
||||
addLog('页面加载完成');
|
||||
addLog('html2canvas-pro 重构版本已加载');
|
||||
addLog('点击按钮开始测试...');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||
import tsparser from '@typescript-eslint/parser';
|
||||
import prettier from 'eslint-plugin-prettier';
|
||||
import prettierConfig from 'eslint-config-prettier';
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json', './tests/tsconfig.json'],
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint,
|
||||
prettier: prettier,
|
||||
},
|
||||
rules: {
|
||||
...prettierConfig.rules,
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }],
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/class-name-casing': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jsdom',
|
||||
roots: ['src']
|
||||
};
|
||||
@@ -0,0 +1,300 @@
|
||||
// Karma configuration
|
||||
// Generated on Sat Aug 05 2017 23:42:26 GMT+0800 (Malay Peninsula Standard Time)
|
||||
|
||||
const path = require('path');
|
||||
const simctl = require('node-simctl');
|
||||
const iosSimulator = require('appium-ios-simulator');
|
||||
const listenAddress = 'localhost';
|
||||
const port = 9876;
|
||||
|
||||
const log = require('karma/lib/logger').create('launcher:MobileSafari');
|
||||
|
||||
module.exports = function(config) {
|
||||
// https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md
|
||||
const launchers = {
|
||||
Safari_IOS_9: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
platform: 'iOS',
|
||||
sdk: '9.0'
|
||||
},
|
||||
Safari_IOS_10: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
platform: 'iOS',
|
||||
sdk: '10.0'
|
||||
},
|
||||
Safari_IOS_12: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
platform: 'iOS',
|
||||
sdk: '12.4'
|
||||
},
|
||||
Safari_IOS_13: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 8',
|
||||
platform: 'iOS',
|
||||
sdk: '13.7'
|
||||
},
|
||||
Safari_IOS_14: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 8',
|
||||
platform: 'iOS',
|
||||
sdk: '14.4'
|
||||
},
|
||||
Safari_IOS_15_0: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 13',
|
||||
platform: 'iOS',
|
||||
sdk: '15.0'
|
||||
},
|
||||
Safari_IOS_15: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 13',
|
||||
platform: 'iOS',
|
||||
sdk: '15.2'
|
||||
},
|
||||
SauceLabs_IE9: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '9.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
SauceLabs_IE10: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '10.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
SauceLabs_IE11: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '11.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
SauceLabs_Edge18: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'MicrosoftEdge',
|
||||
version: '18.17763',
|
||||
platform: 'Windows 10'
|
||||
},
|
||||
SauceLabs_Android4: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Browser',
|
||||
platform: 'Android',
|
||||
version: '4.4',
|
||||
device: 'Android Emulator',
|
||||
},
|
||||
SauceLabs_iOS10_3: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Safari',
|
||||
platform: 'iOS',
|
||||
version: '10.3',
|
||||
device: 'iPhone 7 Plus Simulator'
|
||||
},
|
||||
SauceLabs_iOS9_3: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Safari',
|
||||
platform: 'iOS',
|
||||
version: '9.3',
|
||||
device: 'iPhone 6 Plus Simulator'
|
||||
},
|
||||
IE_9: {
|
||||
base: 'IE',
|
||||
'x-ua-compatible': 'IE=EmulateIE9',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
IE_10: {
|
||||
base: 'IE',
|
||||
'x-ua-compatible': 'IE=EmulateIE10',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
IE_11: {
|
||||
base: 'IE',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
Safari_Stable: {
|
||||
base: 'SafariNative'
|
||||
},
|
||||
Chrome_Stable: {
|
||||
base: 'ChromeHeadless'
|
||||
},
|
||||
Firefox_Stable: {
|
||||
base: 'Firefox'
|
||||
}
|
||||
};
|
||||
|
||||
const ciLauncher = launchers[process.env.TARGET_BROWSER];
|
||||
|
||||
const customLaunchers = ciLauncher ? {target_browser: ciLauncher} : {
|
||||
stable_chrome: {
|
||||
base: 'ChromeHeadless'
|
||||
},
|
||||
stable_firefox: {
|
||||
base: 'Firefox'
|
||||
}
|
||||
};
|
||||
|
||||
const injectTypedArrayPolyfills = function(files) {
|
||||
files.unshift({
|
||||
pattern: path.resolve(__dirname, './node_modules/js-polyfills/typedarray.js'),
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false
|
||||
});
|
||||
};
|
||||
|
||||
injectTypedArrayPolyfills.$inject = ['config.files'];
|
||||
|
||||
const MobileSafari = function(baseBrowserDecorator, args) {
|
||||
if(process.platform !== "darwin"){
|
||||
log.error("This launcher only works in MacOS.");
|
||||
this._process.kill();
|
||||
return;
|
||||
}
|
||||
baseBrowserDecorator(this);
|
||||
this.on('start', url => {
|
||||
simctl.getDevices(args.sdk, args.platform).then(devices => {
|
||||
const d = devices.find(d => {
|
||||
return d.name === args.name;
|
||||
});
|
||||
|
||||
if (!d) {
|
||||
log.error(`No device found for sdk ${args.sdk} with name ${args.name}`);
|
||||
log.info(`Available devices:`, devices);
|
||||
this._process.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
return iosSimulator.getSimulator(d.udid).then(device => {
|
||||
return simctl.bootDevice(d.udid).then(() => device);
|
||||
}).then(device => {
|
||||
return device.waitForBoot(60 * 5 * 1000).then(() => {
|
||||
return device.openUrl(url);
|
||||
});
|
||||
});
|
||||
}).catch(e => {
|
||||
console.log('err,', e);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
MobileSafari.prototype = {
|
||||
name: 'MobileSafari',
|
||||
DEFAULT_CMD: {
|
||||
darwin: '/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator',
|
||||
},
|
||||
ENV_CMD: null,
|
||||
};
|
||||
|
||||
MobileSafari.$inject = ['baseBrowserDecorator', 'args'];
|
||||
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '',
|
||||
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['mocha', 'inline-mocha-fix'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'build/testrunner.js',
|
||||
{ pattern: './tests/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
{ pattern: './dist/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
{ pattern: './node_modules/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
],
|
||||
|
||||
plugins: [
|
||||
'karma-*',
|
||||
{
|
||||
'framework:inline-mocha-fix': ['factory', injectTypedArrayPolyfills]
|
||||
},
|
||||
{
|
||||
'launcher:MobileSafari': ['type', MobileSafari]
|
||||
}
|
||||
],
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
],
|
||||
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
},
|
||||
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['dots', 'junit'],
|
||||
|
||||
junitReporter: {
|
||||
outputDir: 'tmp/junit/'
|
||||
},
|
||||
|
||||
// web server listen address,
|
||||
listenAddress,
|
||||
|
||||
// web server port
|
||||
port,
|
||||
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: Object.keys(customLaunchers),
|
||||
|
||||
|
||||
customLaunchers,
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: true,
|
||||
|
||||
// Concurrency level
|
||||
// how many browser should be started simultaneous
|
||||
concurrency: 5,
|
||||
|
||||
proxies: {
|
||||
'/dist': `http://localhost:${port}/base/dist`,
|
||||
'/node_modules': `http://localhost:${port}/base/node_modules`,
|
||||
'/tests': `http://localhost:${port}/base/tests`,
|
||||
'/assets': `http://localhost:${port}/base/tests/assets`
|
||||
},
|
||||
|
||||
client: {
|
||||
mocha: {
|
||||
// change Karma's debug.html to the mocha web reporter
|
||||
reporter: 'html'
|
||||
}
|
||||
},
|
||||
|
||||
captureTimeout: 300000,
|
||||
|
||||
// Safari on CI (especially newer macOS runners) can be slow to establish
|
||||
// socket.io heartbeats; generous timeouts avoid false ping-timeout failures.
|
||||
pingTimeout: 120000,
|
||||
browserSocketTimeout: 120000,
|
||||
browserDisconnectTimeout: 120000,
|
||||
browserDisconnectTolerance: 3,
|
||||
|
||||
browserNoActivityTimeout: 1200000
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"name": "html2canvas-pro",
|
||||
"description": "Screenshots with JavaScript. Next generation!",
|
||||
"type": "module",
|
||||
"main": "dist/html2canvas-pro.js",
|
||||
"module": "dist/html2canvas-pro.esm.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"browser": "dist/html2canvas-pro.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"import": "./dist/html2canvas-pro.esm.js",
|
||||
"require": "./dist/html2canvas-pro.js",
|
||||
"default": "./dist/html2canvas-pro.esm.js"
|
||||
}
|
||||
},
|
||||
"version": "2.0.4",
|
||||
"author": {
|
||||
"name": "yorickshan",
|
||||
"email": "yorickshan@gmail.com",
|
||||
"url": "https://github.com/yorickshan"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"html2canvas",
|
||||
"screenshot"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"homepage": "https://yorickshan.github.io/html2canvas-pro/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yorickshan/html2canvas-pro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^20.3.0",
|
||||
"@commitlint/config-conventional": "^20.2.0",
|
||||
"@rollup/plugin-commonjs": "^29.0.0",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-typescript": "^12.3.0",
|
||||
"@types/chai": "^5.2.3",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/filenamify-url": "^2.0.1",
|
||||
"@types/glob": "^9.0.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/jest-image-snapshot": "^6.4.0",
|
||||
"@types/karma": "^6.3.0",
|
||||
"@types/mkdirp": "^2.0.0",
|
||||
"@types/mocha": "^10.0.7",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/platform": "^1.3.4",
|
||||
"@types/promise-polyfill": "^6.0.3",
|
||||
"@types/serve-index": "^1.9.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.50.0",
|
||||
"@typescript-eslint/parser": "^8.50.0",
|
||||
"appium-ios-simulator": "^8.0.9",
|
||||
"base64-arraybuffer": "1.0.2",
|
||||
"body-parser": "^2.2.1",
|
||||
"chai": "6.2.2",
|
||||
"change-case": "^5.4.4",
|
||||
"conventional-changelog-cli": "^5.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"express": "^5.2.1",
|
||||
"filenamify-url": "4.0.0",
|
||||
"glob": "^13.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
"jest-image-snapshot": "^6.4.0",
|
||||
"jquery": "^3.5.1",
|
||||
"js-polyfills": "^0.1.42",
|
||||
"karma": "^6.3.2",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-edge-launcher": "^0.4.2",
|
||||
"karma-firefox-launcher": "^2.1.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-junit-reporter": "^2.0.1",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-safarinative-launcher": "^1.1.0",
|
||||
"karma-sauce-launcher": "^4.3.6",
|
||||
"lint-staged": "^16.2.7",
|
||||
"mocha": "^11.7.5",
|
||||
"node-simctl": "^8.1.2",
|
||||
"platform": "^1.3.6",
|
||||
"prettier": "^3.3.3",
|
||||
"puppeteer": "^24.34.0",
|
||||
"replace-in-file": "^8.4.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.24.2",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"serve-index": "^1.9.1",
|
||||
"slash": "5.1.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"ts-jest": "^29.1.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.22.3",
|
||||
"typescript": "^5.5.4",
|
||||
"uglify-js": "^3.13.10",
|
||||
"vitepress": "^1.3.0",
|
||||
"yargs": "^18.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist/ && rimraf build/ && mkdirp dist && mkdirp build",
|
||||
"build": "tsc --module commonjs && npx tsx --tsconfig tsconfig.json ./node_modules/.bin/rollup -c rollup.config.ts && npm run build:create-reftest-list && npx tsx --tsconfig tests/tsconfig.json ./node_modules/.bin/rollup -c tests/rollup.config.ts && npm run build:minify",
|
||||
"build:testrunner": "npx tsx --tsconfig tests/tsconfig.json ./node_modules/.bin/rollup -c tests/rollup.config.ts",
|
||||
"build:minify": "uglifyjs --compress --comments /^!/ -o dist/html2canvas-pro.min.js --mangle -- dist/html2canvas-pro.js",
|
||||
"build:reftest-result-list": "npx tsx scripts/create-reftest-result-list.ts",
|
||||
"build:create-reftest-list": "npx tsx scripts/create-reftest-list.ts tests/reftests/ignore.txt build/reftests.js",
|
||||
"release": "sh scripts/version-upgrade.sh",
|
||||
"format": "prettier --write \"{src,tests,scripts}/**/*.ts\"",
|
||||
"lint": "eslint src/**/*.ts --max-warnings 0",
|
||||
"test": "npm run lint && npm run unittest && npm run karma",
|
||||
"unittest": "jest",
|
||||
"reftests-diff": "mkdirp tmp/snapshots && jest --roots=tests --testMatch=**/reftest-diff.ts",
|
||||
"prekarma": "node scripts/ensure-karma-prereqs.mjs",
|
||||
"karma": "tsx tests/karma",
|
||||
"watch": "rollup -c rollup.config.ts -w",
|
||||
"watch:unittest": "mocha --require tsx/register --watch-extensions ts -w src/**/__tests__/*.ts",
|
||||
"start": "npx tsx tests/server --port=8080 --cors=8081",
|
||||
"commitlint": "commitlint --config .commitlintrc.json -e -V",
|
||||
"tag": "sh scripts/create-tag.sh",
|
||||
"prepare": "if [ \"$SKIP_PREPARE\" != \"true\" ]; then husky install; fi",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"{src,tests}/**/*.ts": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user