mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp.git
synced 2026-06-21 09:20:14 +08:00
144 lines
3.6 KiB
JavaScript
144 lines
3.6 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import valueParser from 'postcss-value-parser';
|
|
import vueTemplateCompiler from 'vue-template-compiler';
|
|
|
|
const roots = ['pages', 'components'];
|
|
const extraFiles = ['App.vue', 'uni.scss'];
|
|
const styleExtensions = new Set(['.css', '.scss']);
|
|
|
|
function listFiles(dir) {
|
|
const result = [];
|
|
for (const entry of readdirSync(dir)) {
|
|
const fullPath = path.join(dir, entry);
|
|
const stat = statSync(fullPath);
|
|
if (stat.isDirectory()) {
|
|
result.push(...listFiles(fullPath));
|
|
} else {
|
|
result.push(fullPath);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function stripCssCommentsPreserveOffsets(source) {
|
|
let output = '';
|
|
let quote = null;
|
|
|
|
for (let i = 0; i < source.length; i += 1) {
|
|
const char = source[i];
|
|
const next = source[i + 1];
|
|
|
|
if (quote) {
|
|
output += char;
|
|
if (char === '\\') {
|
|
i += 1;
|
|
output += source[i] || '';
|
|
} else if (char === quote) {
|
|
quote = null;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (char === '"' || char === "'") {
|
|
quote = char;
|
|
output += char;
|
|
continue;
|
|
}
|
|
|
|
if (char === '/' && next === '*') {
|
|
output += ' ';
|
|
i += 2;
|
|
while (i < source.length && !(source[i] === '*' && source[i + 1] === '/')) {
|
|
output += source[i] === '\n' ? '\n' : ' ';
|
|
i += 1;
|
|
}
|
|
output += ' ';
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
if (char === '/' && next === '/') {
|
|
output += ' ';
|
|
i += 2;
|
|
while (i < source.length && source[i] !== '\n') {
|
|
output += ' ';
|
|
i += 1;
|
|
}
|
|
if (source[i] === '\n') {
|
|
output += '\n';
|
|
}
|
|
continue;
|
|
}
|
|
|
|
output += char;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
function lineForOffset(source, offset) {
|
|
return source.slice(0, offset).split('\n').length;
|
|
}
|
|
|
|
function collectRootStaticStyleUrls(filePath, styleSource, sourceOffset = 0, originalSource = styleSource) {
|
|
const stripped = stripCssCommentsPreserveOffsets(styleSource);
|
|
const findings = [];
|
|
|
|
valueParser(stripped).walk((node) => {
|
|
if (node.type !== 'function' || node.value.toLowerCase() !== 'url') {
|
|
return;
|
|
}
|
|
|
|
const url = valueParser.stringify(node.nodes).trim().replace(/^['"]|['"]$/g, '');
|
|
if (!url.startsWith('/static/')) {
|
|
return;
|
|
}
|
|
|
|
findings.push({
|
|
filePath,
|
|
line: lineForOffset(originalSource, sourceOffset + node.sourceIndex),
|
|
url,
|
|
});
|
|
});
|
|
|
|
return findings;
|
|
}
|
|
|
|
function styleSourcesForFile(filePath) {
|
|
const source = readFileSync(filePath, 'utf8');
|
|
if (filePath.endsWith('.vue')) {
|
|
const descriptor = vueTemplateCompiler.parseComponent(source);
|
|
return descriptor.styles.map((style) => ({
|
|
source: style.content,
|
|
sourceOffset: style.start,
|
|
originalSource: source,
|
|
}));
|
|
}
|
|
|
|
return [{ source, sourceOffset: 0, originalSource: source }];
|
|
}
|
|
|
|
test('style url() references do not use H5-root /static paths', () => {
|
|
const files = [
|
|
...roots.flatMap((root) => listFiles(root)),
|
|
...extraFiles,
|
|
].filter((filePath) => filePath.endsWith('.vue') || styleExtensions.has(path.extname(filePath)));
|
|
|
|
const findings = files.flatMap((filePath) =>
|
|
styleSourcesForFile(filePath).flatMap(({ source, sourceOffset, originalSource }) =>
|
|
collectRootStaticStyleUrls(filePath, source, sourceOffset, originalSource)
|
|
)
|
|
);
|
|
|
|
assert.deepEqual(
|
|
findings,
|
|
[],
|
|
`style url() must use relative static paths so H5 /h5 deployments do not request site-root assets:\n${
|
|
findings.map((item) => `${item.filePath}:${item.line} ${item.url}`).join('\n')
|
|
}`
|
|
);
|
|
});
|