1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-13 15:15:45 +02:00

Merge branch 'develop' into room

This commit is contained in:
syuilo
2026-04-02 12:53:16 +09:00
33 changed files with 2736 additions and 1877 deletions

View File

@@ -1,3 +1,15 @@
## Unreleased
### General
-
### Client
-
### Server
- Fix: `/api-doc` にアクセスできない問題を修正
## 2026.3.2
### General

View File

@@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2026.3.2-beta.0",
"version": "2026.3.2",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@10.32.1",
"packageManager": "pnpm@10.33.0",
"workspaces": [
"packages/misskey-js",
"packages/i18n",
@@ -59,23 +59,23 @@
"ignore-walk": "8.0.0",
"js-yaml": "4.1.1",
"postcss": "8.5.8",
"tar": "7.5.11",
"terser": "5.46.0"
"tar": "7.5.13",
"terser": "5.46.1"
},
"devDependencies": {
"@eslint/js": "9.39.4",
"@misskey-dev/eslint-plugin": "2.1.0",
"@types/js-yaml": "4.0.9",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"@typescript/native-preview": "7.0.0-dev.20260116.1",
"cross-env": "10.1.0",
"cypress": "15.11.0",
"cypress": "15.13.0",
"eslint": "9.39.4",
"globals": "17.4.0",
"ncp": "2.0.0",
"pnpm": "10.32.1",
"pnpm": "10.33.0",
"start-server-and-test": "2.1.5",
"typescript": "5.9.3"
},

View File

@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Misskey API</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<script
id="api-reference"
data-url="/api.json"></script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -41,17 +41,17 @@
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
"@swc/core-darwin-arm64": "1.15.18",
"@swc/core-darwin-x64": "1.15.18",
"@swc/core-darwin-arm64": "1.15.21",
"@swc/core-darwin-x64": "1.15.21",
"@swc/core-freebsd-x64": "1.3.11",
"@swc/core-linux-arm-gnueabihf": "1.15.18",
"@swc/core-linux-arm64-gnu": "1.15.18",
"@swc/core-linux-arm64-musl": "1.15.18",
"@swc/core-linux-x64-gnu": "1.15.18",
"@swc/core-linux-x64-musl": "1.15.18",
"@swc/core-win32-arm64-msvc": "1.15.18",
"@swc/core-win32-ia32-msvc": "1.15.18",
"@swc/core-win32-x64-msvc": "1.15.18",
"@swc/core-linux-arm-gnueabihf": "1.15.21",
"@swc/core-linux-arm64-gnu": "1.15.21",
"@swc/core-linux-arm64-musl": "1.15.21",
"@swc/core-linux-x64-gnu": "1.15.21",
"@swc/core-linux-x64-musl": "1.15.21",
"@swc/core-win32-arm64-msvc": "1.15.21",
"@swc/core-win32-ia32-msvc": "1.15.21",
"@swc/core-win32-x64-msvc": "1.15.21",
"@tensorflow/tfjs": "4.22.0",
"@tensorflow/tfjs-node": "4.22.0",
"bufferutil": "4.1.0",
@@ -71,30 +71,30 @@
"utf-8-validate": "6.0.6"
},
"dependencies": {
"@aws-sdk/client-s3": "3.1008.0",
"@aws-sdk/lib-storage": "3.1008.0",
"@aws-sdk/client-s3": "3.1016.0",
"@aws-sdk/lib-storage": "3.1016.0",
"@discordapp/twemoji": "16.0.1",
"@fastify/accepts": "5.0.4",
"@fastify/cors": "11.2.0",
"@fastify/express": "4.0.4",
"@fastify/http-proxy": "11.4.1",
"@fastify/http-proxy": "11.4.2",
"@fastify/multipart": "9.4.0",
"@fastify/static": "9.0.0",
"@kitajs/html": "4.2.13",
"@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.2.5",
"@napi-rs/canvas": "0.1.96",
"@nestjs/common": "11.1.16",
"@nestjs/core": "11.1.16",
"@nestjs/testing": "11.1.16",
"@napi-rs/canvas": "0.1.97",
"@nestjs/common": "11.1.17",
"@nestjs/core": "11.1.17",
"@nestjs/testing": "11.1.17",
"@peertube/http-signature": "1.7.0",
"@sentry/node": "10.43.0",
"@sentry/profiling-node": "10.43.0",
"@sentry/node": "10.45.0",
"@sentry/profiling-node": "10.45.0",
"@simplewebauthn/server": "13.3.0",
"@sinonjs/fake-timers": "15.1.1",
"@smithy/node-http-handler": "4.4.16",
"@smithy/node-http-handler": "4.5.0",
"@swc/cli": "0.8.0",
"@swc/core": "1.15.18",
"@swc/core": "1.15.21",
"@twemoji/parser": "16.0.0",
"accepts": "1.3.8",
"ajv": "8.18.0",
@@ -112,35 +112,35 @@
"content-disposition": "1.0.1",
"date-fns": "4.1.0",
"deep-email-validator": "0.1.21",
"fastify": "5.8.2",
"fastify": "5.8.4",
"fastify-raw-body": "5.0.0",
"feed": "5.2.0",
"file-type": "21.3.2",
"file-type": "21.3.4",
"fluent-ffmpeg": "2.1.3",
"form-data": "4.0.5",
"got": "14.6.6",
"hpagent": "1.2.0",
"http-link-header": "1.1.3",
"i18n": "workspace:*",
"ioredis": "5.10.0",
"ioredis": "5.10.1",
"ip-cidr": "4.0.2",
"ipaddr.js": "2.3.0",
"is-svg": "6.1.0",
"json5": "2.2.3",
"jsonld": "9.0.0",
"juice": "11.1.1",
"meilisearch": "0.55.0",
"meilisearch": "0.56.0",
"mfm-js": "0.25.0",
"mime-types": "3.0.2",
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.202508261828",
"nanoid": "5.1.6",
"nanoid": "5.1.7",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
"node-html-parser": "7.1.0",
"nodemailer": "8.0.2",
"nsfwjs": "4.2.0",
"nodemailer": "8.0.3",
"nsfwjs": "4.3.0",
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
@@ -157,14 +157,14 @@
"rename": "1.0.4",
"rss-parser": "3.13.0",
"rxjs": "7.8.2",
"sanitize-html": "2.17.1",
"sanitize-html": "2.17.2",
"secure-json-parse": "4.1.0",
"semver": "7.7.4",
"sharp": "0.33.5",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"systeminformation": "5.31.4",
"systeminformation": "5.31.5",
"tinycolor2": "1.6.0",
"tmp": "0.2.5",
"tsc-alias": "1.8.16",
@@ -172,14 +172,14 @@
"ulid": "3.0.2",
"vary": "1.1.2",
"web-push": "3.6.7",
"ws": "8.19.0",
"ws": "8.20.0",
"xev": "3.0.2"
},
"devDependencies": {
"@jest/globals": "29.7.0",
"@kitajs/ts-html-plugin": "4.1.4",
"@nestjs/platform-express": "11.1.16",
"@sentry/vue": "10.43.0",
"@nestjs/platform-express": "11.1.17",
"@sentry/vue": "10.45.0",
"@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.39",
"@types/accepts": "1.3.7",
@@ -197,7 +197,7 @@
"@types/nodemailer": "7.0.11",
"@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2",
"@types/pg": "8.18.0",
"@types/pg": "8.20.0",
"@types/qrcode": "1.5.6",
"@types/random-seed": "0.3.5",
"@types/ratelimiter": "3.4.6",
@@ -212,8 +212,8 @@
"@types/vary": "1.1.3",
"@types/web-push": "3.6.4",
"@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"aws-sdk-client-mock": "4.1.0",
"cbor": "10.0.12",
"cross-env": "10.1.0",
@@ -225,9 +225,9 @@
"jest-mock": "29.7.0",
"js-yaml": "4.1.1",
"nodemon": "3.1.14",
"pid-port": "2.0.1",
"pid-port": "2.1.0",
"simple-oauth2": "5.1.0",
"supertest": "7.2.2",
"vite": "7.3.1"
"vite": "8.0.2"
}
}

View File

@@ -10,7 +10,7 @@ import { Injectable } from '@nestjs/common';
import { Mutex } from 'async-mutex';
import fetch from 'node-fetch';
import { bindThis } from '@/decorators.js';
import type { NSFWJS, PredictionType } from 'nsfwjs';
import type { NSFWJS, PredictionType } from 'nsfwjs/core';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@@ -43,7 +43,7 @@ export class AiService {
tf.env().global.fetch = fetch;
if (this.model == null) {
const nsfw = await import('nsfwjs');
const nsfw = await import('nsfwjs/core');
await this.modelLoadMutex.runExclusive(async () => {
if (this.model == null) {
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });

View File

@@ -3,16 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { genOpenapiSpec } from './gen-spec.js';
import { ApiDocPage } from './api-doc.js';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
const staticAssets = fileURLToPath(new URL('../../../../assets/', import.meta.url));
@Injectable()
export class OpenApiServerService {
constructor(
@@ -25,7 +23,8 @@ export class OpenApiServerService {
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.get('/api-doc', async (_request, reply) => {
reply.header('Cache-Control', 'public, max-age=86400');
return await reply.sendFile('/api-doc.html', staticAssets);
reply.type('text/html; charset=utf-8');
reply.send(await ApiDocPage());
});
fastify.get('/api.json', (_request, reply) => {
reply.header('Cache-Control', 'public, max-age=600');

View File

@@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export function ApiDocPage() {
return (
<>
{'<!DOCTYPE html>'}
<html>
<head>
<meta charset="UTF-8" />
<title>Misskey API</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
{`body { margin: 0; padding: 0; }`}
</style>
</head>
<body>
<script id="api-reference" data-url="/api.json"></script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
</>
);
}

View File

@@ -3,10 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { parseAst } from 'vite';
import { parseAst } from 'rolldown/parseAst';
import * as estreeWalker from 'estree-walker';
import { assertNever, assertType } from '../utils.js';
import type { AstNode, ProgramNode } from 'rollup';
import type { ESTree as RolldownESTree } from 'rolldown/utils';
import type { AstNode } from 'rollup';
import type * as estree from 'estree';
import type { LocaleInliner, TextModification } from '../locale-inliner.js';
import type { Logger } from '../logger.js';
@@ -17,7 +18,7 @@ interface WalkerContext {
}
export function collectModifications(sourceCode: string, fileName: string, fileLogger: Logger, inliner: LocaleInliner): TextModification[] {
let programNode: ProgramNode;
let programNode: RolldownESTree.Program;
try {
programNode = parseAst(sourceCode);
} catch (err) {
@@ -35,7 +36,8 @@ export function collectModifications(sourceCode: string, fileName: string, fileL
// 1) replace all `scripts/` path literals with locale code
// 2) replace all `localStorage.getItem("lang")` with `localeName` variable
// 3) replace all `await window.fetch(`/assets/locales/${d}.${x}.json`).then(u=>u.json())` with `localeJson` variable
estreeWalker.walk(programNode, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(estreeWalker.walk as any)(programNode, {
enter(this: WalkerContext, node: Node) {
assertType<AstNode>(node);
@@ -118,8 +120,9 @@ export function collectModifications(sourceCode: string, fileName: string, fileL
// Check if the identifier is already declared in the file.
// If it is, we may overwrite it and cause issues so we skip inlining
let isSupported = true;
estreeWalker.walk(programNode, {
enter(node) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(estreeWalker.walk as any)(programNode, {
enter(node: Node) {
if (node.type === 'VariableDeclaration') {
assertType<estree.VariableDeclaration>(node);
for (const id of node.declarations.flatMap(x => declsOfPattern(x.id))) {
@@ -145,8 +148,9 @@ export function collectModifications(sourceCode: string, fileName: string, fileL
const toSkip = new Set();
toSkip.add(i18nImport);
estreeWalker.walk(programNode, {
enter(this: WalkerContext, node, parent, property) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(estreeWalker.walk as any)(programNode, {
enter(this: WalkerContext, node: Node, parent: Node | null, property: string | number | symbol | null | undefined) {
assertType<AstNode>(node);
assertType<AstNode>(parent);
if (toSkip.has(node)) {
@@ -379,7 +383,7 @@ type SpecifierResult =
| { type: 'specifier', localI18nIdentifier: string, importNode: estree.ImportDeclaration & AstNode }
;
function findImportSpecifier(programNode: ProgramNode, i18nFileName: string, i18nSymbol: string): SpecifierResult {
function findImportSpecifier(programNode: RolldownESTree.Program, i18nFileName: string, i18nSymbol: string): SpecifierResult {
const imports = programNode.body.filter(x => x.type === 'ImportDeclaration');
const importNode = imports.find(x => x.source.value === `./${i18nFileName}`) as estree.ImportDeclaration | undefined;
if (!importNode) return { type: 'no-import' };

View File

@@ -12,14 +12,15 @@
"devDependencies": {
"@types/estree": "1.0.8",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"rollup": "4.59.0"
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"rollup": "4.60.0"
},
"dependencies": {
"i18n": "workspace:*",
"estree-walker": "3.0.3",
"i18n": "workspace:*",
"magic-string": "0.30.21",
"vite": "7.3.1"
"rolldown": "1.0.0-rc.11",
"vite": "8.0.2"
}
}

View File

@@ -4,11 +4,11 @@
*/
import * as estreeWalker from 'estree-walker';
import MagicString from 'magic-string';
import { RolldownMagicString } from 'rolldown';
import { assertType } from './utils.js';
import type { ESTree } from 'rolldown/utils';
import type { Plugin } from 'vite';
import type { CallExpression, Expression, Program } from 'estree';
import type { AstNode } from 'rollup';
import type { CallExpression, Expression } from 'estree';
// This plugin transforms `unref(i18n)` to `i18n` in the code, which is useful for removing unnecessary unref calls
// and helps locale inliner runs after vite build to inline the locale data into the final build.
@@ -23,12 +23,13 @@ export function pluginRemoveUnrefI18n(
} = {}): Plugin {
return {
name: 'UnwindCssModuleClassName',
renderChunk(code) {
renderChunk(code, _chunk, _options, meta) {
if (!code.includes('unref(i18n)')) return null;
const ast = this.parse(code) as Program;
const magicString = new MagicString(code);
estreeWalker.walk(ast, {
enter(node) {
const ast = this.parse(code);
const magicString = meta.magicString ?? new RolldownMagicString(code);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(estreeWalker.walk as any)(ast, {
enter(node: ESTree.Node) {
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'unref'
&& node.arguments.length === 1) {
// calls to unref with single argument
@@ -36,18 +37,16 @@ export function pluginRemoveUnrefI18n(
if (arg.type === 'Identifier' && arg.name === i18nSymbolName) {
// this is unref(i18n) so replace it with i18n
// to replace, remove the 'unref(' and the trailing ')'
assertType<CallExpression & AstNode>(node);
assertType<Expression & AstNode>(arg);
assertType<CallExpression>(node);
assertType<Expression>(arg);
magicString.remove(node.start, arg.start);
magicString.remove(arg.end, node.end);
}
}
},
});
return {
code: magicString.toString(),
map: magicString.generateMap({ hires: true }),
};
return magicString;
},
};
}

View File

@@ -3,7 +3,7 @@ import url from 'node:url';
import path from 'node:path';
import { execa } from 'execa';
import locales from 'i18n';
import { LocaleInliner } from '../frontend-builder/locale-inliner.js'
import { LocaleInliner } from '../frontend-builder/locale-inliner.js';
import { createLogger } from '../frontend-builder/logger';
// requires node 21 or later

View File

@@ -1,7 +1,10 @@
// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import JSON5 from 'json5';
import { Plugin } from 'rollup';
import { Plugin } from 'vite';
import { createFilter, dataToEsm } from '@rollup/pluginutils';
import { RollupJsonOptions } from '@rollup/plugin-json';

View File

@@ -12,7 +12,6 @@
"dependencies": {
"@discordapp/twemoji": "16.0.1",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.3",
"@rollup/pluginutils": "5.3.0",
"@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.5",
@@ -25,12 +24,10 @@
"mfm-js": "0.25.0",
"misskey-js": "workspace:*",
"punycode.js": "2.3.1",
"rollup": "4.59.0",
"sass": "1.98.0",
"rollup": "4.60.0",
"shiki": "3.23.0",
"tinycolor2": "1.6.0",
"uuid": "13.0.0",
"vite": "7.3.1",
"vue": "3.5.30"
},
"devDependencies": {
@@ -43,25 +40,27 @@
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@vitest/coverage-v8": "4.1.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"@vitest/coverage-v8": "4.1.1",
"@vue/runtime-core": "3.5.30",
"acorn": "8.16.0",
"cross-env": "10.1.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.8.0",
"happy-dom": "20.8.4",
"happy-dom": "20.8.8",
"intersection-observer": "0.12.2",
"micromatch": "4.0.8",
"msw": "2.12.10",
"msw": "2.12.14",
"nodemon": "3.1.14",
"prettier": "3.8.1",
"sass-embedded": "1.98.0",
"start-server-and-test": "2.1.5",
"tsx": "4.21.0",
"vite": "8.0.2",
"vite-plugin-turbosnap": "1.0.3",
"vue-component-type-helpers": "3.2.5",
"vue-component-type-helpers": "3.2.6",
"vue-eslint-parser": "10.4.0",
"vue-tsc": "3.2.5"
"vue-tsc": "3.2.6"
}
}

View File

@@ -7,10 +7,10 @@ import { promises as fsp } from 'fs';
import locales from 'i18n';
import meta from '../../package.json';
import packageInfo from './package.json' with { type: 'json' };
import pluginJson5 from './vite.json5.js';
import pluginJson5 from './lib/vite-plugin-json5.js';
import { pluginRemoveUnrefI18n } from '../frontend-builder/rollup-plugin-remove-unref-i18n';
const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null;
const url = process.env.NODE_ENV === 'development' ? (yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')) as any).url : null;
const host = url ? (new URL(url)).hostname : undefined;
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
@@ -113,11 +113,6 @@ export function getConfig(): UserConfig {
}
},
},
preprocessorOptions: {
scss: {
api: 'modern-compiler',
},
},
},
define: {
@@ -137,7 +132,10 @@ export function getConfig(): UserConfig {
'safari16',
],
manifest: 'manifest.json',
rollupOptions: {
rolldownOptions: {
experimental: {
nativeMagicString: true,
},
input: {
i18n: './src/i18n.ts',
entry: './src/boot.ts',
@@ -145,10 +143,15 @@ export function getConfig(): UserConfig {
external: externalPackages.map(p => p.match),
preserveEntrySignatures: 'allow-extension',
output: {
manualChunks: {
vue: ['vue'],
// dependencies of i18n.ts
'config': ['@@/js/config.js'],
codeSplitting: {
groups: [{
name: 'vue',
test: /node_modules[\\/]vue/,
}, {
// dependencies of i18n.ts
name: 'config',
test: /@@[\\/]js[\\/]config\.js/,
}],
},
entryFileNames: `scripts/${localesHash}-[hash:8].js`,
chunkFileNames: `scripts/${localesHash}-[hash:8].js`,

View File

@@ -22,8 +22,8 @@
},
"devDependencies": {
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"esbuild": "0.27.4",
"eslint-plugin-vue": "10.8.0",
"nodemon": "3.1.14",

View File

@@ -3,15 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { parse } from 'acorn';
import { generate } from 'astring';
import { describe, expect, it } from 'vitest';
import { normalizeClass, unwindCssModuleClassName } from './rollup-plugin-unwind-css-module-class-name.js';
import type * as estree from 'estree';
import { parseAst } from 'rolldown/parseAst';
import type { ESTree } from 'rolldown/utils';
import { RolldownMagicString } from 'rolldown';
function parseExpression(code: string): estree.Expression {
const program = parse(code, { ecmaVersion: 'latest', sourceType: 'module' }) as unknown as estree.Program;
const statement = program.body[0] as estree.ExpressionStatement;
function parseExpression(code: string): ESTree.Expression {
const program = parseAst(code, { sourceType: 'module' });
const statement = program.body[0] as ESTree.ExpressionStatement;
return statement.expression;
}
@@ -57,7 +57,7 @@ describe(normalizeClass.name, () => {
});
it('Composition API (standard)', () => {
const ast = parse(`
const code = `
import { c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
import { M as MkContainer } from './MkContainer-!~{03M}~.js';
import { b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode } from './vue-!~{002}~.js';
@@ -170,17 +170,19 @@ const cssModules = {
const index_photos = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
export { index_photos as default };
`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' });
unwindCssModuleClassName(ast);
expect(generate(ast)).toBe(`
import {c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc} from './app-!~{001}~.js';
import {M as MkContainer} from './MkContainer-!~{03M}~.js';
import {b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode} from './vue-!~{002}~.js';
`.slice(1);
const ast = parseAst(code, { sourceType: 'module' });
const magicString = new RolldownMagicString(code);
unwindCssModuleClassName(ast, magicString);
expect(magicString.toString()).toBe(
`
import { c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
import { M as MkContainer } from './MkContainer-!~{03M}~.js';
import { b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode } from './vue-!~{002}~.js';
import './photoswipe-!~{003}~.js';
const _hoisted_1 = createBaseVNode("i", {
class: "ti ti-photo"
}, null, -1);
const index_photos = defineComponent({
const _hoisted_1 = /* @__PURE__ */ createBaseVNode("i", { class: "ti ti-photo" }, null, -1);
const index_photos = /* @__PURE__ */ defineComponent({
__name: "index.photos",
props: {
user: {}
@@ -193,12 +195,20 @@ const index_photos = defineComponent({
return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
}
onMounted(() => {
const image = ["image/jpeg", "image/webp", "image/avif", "image/png", "image/gif", "image/apng", "image/vnd.mozilla.apng"];
const image = [
"image/jpeg",
"image/webp",
"image/avif",
"image/png",
"image/gif",
"image/apng",
"image/vnd.mozilla.apng"
];
api("users/notes", {
userId: props.user.id,
fileType: image,
limit: 10
}).then(notes => {
}).then((notes) => {
for (const note of notes) {
for (const file of note.files) {
images.value.push({
@@ -213,60 +223,77 @@ const index_photos = defineComponent({
return (_ctx, _cache) => {
const _component_MkLoading = resolveComponent("MkLoading");
const _component_MkA = resolveComponent("MkA");
return (openBlock(), createBlock(MkContainer, {
return openBlock(), createBlock(MkContainer, {
"max-height": 300,
foldable: true
}, {
icon: withCtx(() => [_hoisted_1]),
header: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).ts.images), 1)]),
default: withCtx(() => [createBaseVNode("div", {
class: "xenMW"
}, [unref(fetching) ? (openBlock(), createBlock(_component_MkLoading, {
key: 0
})) : createCommentVNode("", true), !unref(fetching) && unref(images).length > 0 ? (openBlock(), createElementBlock("div", {
key: 1,
class: "xaZzf"
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(images), image => {
return (openBlock(), createBlock(_component_MkA, {
key: image.note.id + image.file.id,
class: "xtA8t",
to: unref(notePage)(image.note)
}, {
default: withCtx(() => [createVNode(ImgWithBlurhash, {
hash: image.file.blurhash,
src: thumbnail(image.file),
title: image.file.name
}, null, 8, ["hash", "src", "title"])]),
_: 2
}, 1032, ["class", "to"]));
}), 128))], 2)) : createCommentVNode("", true), !unref(fetching) && unref(images).length == 0 ? (openBlock(), createElementBlock("p", {
key: 2,
class: "xhYKj"
}, toDisplayString(unref(i18n).ts.nothing), 3)) : createCommentVNode("", true)], 2)]),
icon: withCtx(() => [
_hoisted_1
]),
header: withCtx(() => [
createTextVNode(toDisplayString(unref(i18n).ts.images), 1)
]),
default: withCtx(() => [
createBaseVNode("div", {
class: "xenMW"
}, [
unref(fetching) ? (openBlock(), createBlock(_component_MkLoading, { key: 0 })) : createCommentVNode("", true),
!unref(fetching) && unref(images).length > 0 ? (openBlock(), createElementBlock("div", {
key: 1,
class: "xaZzf"
}, [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(images), (image) => {
return openBlock(), createBlock(_component_MkA, {
key: image.note.id + image.file.id,
class: "xtA8t",
to: unref(notePage)(image.note)
}, {
default: withCtx(() => [
createVNode(ImgWithBlurhash, {
hash: image.file.blurhash,
src: thumbnail(image.file),
title: image.file.name
}, null, 8, ["hash", "src", "title"])
]),
_: 2
}, 1032, ["class", "to"]);
}), 128))
], 2)) : createCommentVNode("", true),
!unref(fetching) && unref(images).length == 0 ? (openBlock(), createElementBlock("p", {
key: 2,
class: "xhYKj"
}, toDisplayString(unref(i18n).ts.nothing), 3)) : createCommentVNode("", true)
], 2)
]),
_: 1
}));
});
};
}
});
const root = "xenMW";
const stream = "xaZzf";
const img = "xtA8t";
const empty = "xhYKj";
const style0 = {
root: root,
stream: stream,
img: img,
empty: empty
root: root,
stream: stream,
img: img,
empty: empty
};
const cssModules = {
"$style": style0
};
export {index_photos as default};
`.slice(1));
export { index_photos as default };
`.slice(1),
);
});
it('Composition API (with `useCssModule()`)', () => {
const ast = parse(`
const code = `
import { a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup } from './!~{002}~.js';
import { d as store, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js';
@@ -437,11 +464,15 @@ const cssModules = {
const MkDateSeparatedList = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
export { MkDateSeparatedList as M };
`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' });
unwindCssModuleClassName(ast);
expect(generate(ast)).toBe(`
import {a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup} from './!~{002}~.js';
import {d as store, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc} from './app-!~{001}~.js';
`.slice(1);
const ast = parseAst(code, { sourceType: 'module' });
const magicString = new RolldownMagicString(code);
unwindCssModuleClassName(ast, magicString);
expect(magicString.toString()).toBe(
`
import { a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup } from './!~{002}~.js';
import { d as store, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js';
function isDebuggerEnabled(id) {
try {
return localStorage.getItem(\`DEBUG_\${id}\`) !== null;
@@ -458,6 +489,7 @@ function stackTraceInstances() {
}
return stack;
}
const _sfc_main = defineComponent({
props: {
items: {
@@ -485,7 +517,7 @@ const _sfc_main = defineComponent({
default: false
}
},
setup(props, {slots, expose}) {
setup(props, { slots, expose }) {
const $style = useCssModule();
function getDateText(time) {
const date = new Date(time).getDate();
@@ -495,28 +527,40 @@ const _sfc_main = defineComponent({
day: date.toString()
});
}
if (props.items.length === 0) return;
if (props.items.length === 0)
return;
const renderChildrenImpl = () => props.items.map((item, i) => {
if (!slots || !slots.default) return;
if (!slots || !slots.default)
return;
const el = slots.default({
item
})[0];
if (el.key == null && item.id) el.key = item.id;
if (el.key == null && item.id)
el.key = item.id;
if (i !== props.items.length - 1 && new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()) {
const separator = h("div", {
class: $style["separator"],
key: item.id + ":separator"
}, h("p", {
class: $style["date"]
}, [h("span", {
class: $style["date-1"]
}, [h("i", {
class: \`ti ti-chevron-up \${$style["date-1-icon"]}\`
}), getDateText(item.createdAt)]), h("span", {
class: $style["date-2"]
}, [getDateText(props.items[i + 1].createdAt), h("i", {
class: \`ti ti-chevron-down \${$style["date-2-icon"]}\`
})])]));
}, [
h("span", {
class: $style["date-1"]
}, [
h("i", {
class: \`ti ti-chevron-up \${$style["date-1-icon"]}\`
}),
getDateText(item.createdAt)
]),
h("span", {
class: $style["date-2"]
}, [
getDateText(props.items[i + 1].createdAt),
h("i", {
class: \`ti ti-chevron-down \${$style["date-2-icon"]}\`
})
])
]));
return [el, separator];
} else {
if (props.ad && item._shouldInsertAd_) {
@@ -532,17 +576,13 @@ const _sfc_main = defineComponent({
const renderChildren = () => {
const children = renderChildrenImpl();
if (isDebuggerEnabled(6864)) {
const nodes = children.flatMap(node => node ?? []);
const keys = new Set(nodes.map(node => node.key));
const nodes = children.flatMap((node) => node ?? []);
const keys = new Set(nodes.map((node) => node.key));
if (keys.size !== nodes.length) {
const id = crypto.randomUUID();
const instances = stackTraceInstances();
toast(instances.reduce((a, c) => \`\${a} at \${c.type.name}\`, \`[DEBUG_6864 (\${id})]: \${nodes.length - keys.size} duplicated keys found\`));
console.warn({
id,
debugId: 6864,
stack: instances
});
console.warn({ id, debugId: 6864, stack: instances });
}
}
return children;
@@ -555,45 +595,136 @@ const _sfc_main = defineComponent({
el.style.top = "";
el.style.left = "";
}
return () => h(prefer.s.animation ? TransitionGroup : "div", {
class: {
[$style["date-separated-list"]]: true,
[$style["date-separated-list-nogap"]]: props.noGap,
[$style["reversed"]]: props.reversed,
[$style["direction-down"]]: props.direction === "down",
[$style["direction-up"]]: props.direction === "up"
return () => h(
prefer.s.animation ? TransitionGroup : "div",
{
class: {
[$style["date-separated-list"]]: true,
[$style["date-separated-list-nogap"]]: props.noGap,
[$style["reversed"]]: props.reversed,
[$style["direction-down"]]: props.direction === "down",
[$style["direction-up"]]: props.direction === "up"
},
...prefer.s.animation ? {
name: "list",
tag: "div",
onBeforeLeave,
onLeaveCanceled
} : {}
},
...prefer.s.animation ? {
name: "list",
tag: "div",
onBeforeLeave,
onLeaveCanceled
} : {}
}, {
default: renderChildren
});
{ default: renderChildren }
);
}
});
const reversed = "xxiZh";
const separator = "xxeDx";
const date = "xxawD";
const style0 = {
"date-separated-list": "xfKPa",
"date-separated-list-nogap": "xf9zr",
"direction-up": "x7AeO",
"direction-down": "xBIqc",
reversed: reversed,
separator: separator,
date: date,
"date-1": "xwtmh",
"date-1-icon": "xsNPa",
"date-2": "x1xvw",
"date-2-icon": "x9ZiG"
"date-separated-list": "xfKPa",
"date-separated-list-nogap": "xf9zr",
"direction-up": "x7AeO",
"direction-down": "xBIqc",
reversed: reversed,
separator: separator,
date: date,
"date-1": "xwtmh",
"date-1-icon": "xsNPa",
"date-2": "x1xvw",
"date-2-icon": "x9ZiG"
};
const cssModules = {
"$style": style0
};
const MkDateSeparatedList = _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
export {MkDateSeparatedList as M};
const MkDateSeparatedList = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
export { MkDateSeparatedList as M };
`.slice(1));
});
it('Composition API (inlined output)', () => {
const code = `
import { a as normalizeClass, b as defineComponent, c as _export_sfc } from './runtime.js';
const CurrentComponent = /* @__PURE__ */ _export_sfc(defineComponent({
__name: "CurrentComponent",
setup() {
return (e, n) => h("div", {
class: normalizeClass([e.$style.root, "extra"])
}, null, 2);
}
}), [["__cssModules", {
"$style": {
root: "x1234"
}
}]]);
export { CurrentComponent as default };
`.slice(1);
const ast = parseAst(code, { sourceType: 'module' });
const magicString = new RolldownMagicString(code);
unwindCssModuleClassName(ast, magicString);
const output = magicString.toString();
expect(output).toContain('class: "x1234 extra"');
expect(output).toContain('defineComponent({');
expect(output).toContain('}), []);');
expect(output).not.toContain('$style');
});
it('should keep cssModules when unresolved references remain', () => {
const code = `
import { a as normalizeClass, b as defineComponent, c as _export_sfc } from './runtime.js';
const CurrentComponent = /* @__PURE__ */ _export_sfc(defineComponent({
__name: "CurrentComponent",
setup() {
return (e, n) => h("div", {
class: normalizeClass([e.$style.root, e.$style[side]])
}, null, 2);
}
}), [["__cssModules", {
"$style": {
root: "x1234"
}
}]]);
export { CurrentComponent as default };
`.slice(1);
const ast = parseAst(code, { sourceType: 'module' });
const magicString = new RolldownMagicString(code);
unwindCssModuleClassName(ast, magicString);
const output = magicString.toString();
expect(output).toContain('e.$style[side]');
expect(output).toContain('__cssModules');
expect(output).not.toContain('}), []);');
});
it('should inline cssModules references used inside class expressions', () => {
const code = `
import { a as classHelper, b as defineComponent, c as _export_sfc } from './runtime.js';
const CurrentComponent = /* @__PURE__ */ _export_sfc(defineComponent({
__name: "CurrentComponent",
setup() {
return (e, n) => h("div", {
class: classHelper([e.$style.root, { [e.$style.main]: isActive }])
}, null, 2);
}
}), [["__cssModules", {
"$style": {
root: "x1234",
main: "x5678"
}
}]]);
export { CurrentComponent as default };
`.slice(1);
const ast = parseAst(code, { sourceType: 'module' });
const magicString = new RolldownMagicString(code);
unwindCssModuleClassName(ast, magicString);
const output = magicString.toString();
expect(output).toContain('class: classHelper(["x1234", { ["x5678"]: isActive }])');
expect(output).toContain('}), []);');
expect(output).not.toContain('$style');
});

View File

@@ -3,17 +3,16 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { generate } from 'astring';
import { walk } from '../node_modules/estree-walker/src/index.js';
import type * as estree from 'estree';
import type * as estreeWalker from 'estree-walker';
import * as estreeWalker from 'estree-walker';
import type { Plugin } from 'vite';
import type { ESTree } from 'rolldown/utils';
import { RolldownMagicString } from 'rolldown';
function isFalsyIdentifier(identifier: estree.Identifier): boolean {
function isFalsyIdentifier(identifier: Extract<ESTree.Node, { type: 'Identifier' }>): boolean {
return identifier.name === 'undefined' || identifier.name === 'NaN';
}
function normalizeClassWalker(tree: estree.Node, stack: string | undefined): string | null {
function normalizeClassWalker(tree: ESTree.Node, stack: string | undefined): string | null {
if (tree.type === 'Identifier') return isFalsyIdentifier(tree) ? '' : null;
if (tree.type === 'Literal') return typeof tree.value === 'string' ? tree.value : '';
if (tree.type === 'BinaryExpression') {
@@ -26,7 +25,7 @@ function normalizeClassWalker(tree: estree.Node, stack: string | undefined): str
if (tree.type === 'TemplateLiteral') {
if (tree.expressions.some((x) => x.type !== 'Literal' && (x.type !== 'Identifier' || !isFalsyIdentifier(x)))) return null;
return tree.quasis.reduce((a, c, i) => {
const v = i === tree.quasis.length - 1 ? '' : (tree.expressions[i] as Partial<estree.Literal>).value;
const v = i === tree.quasis.length - 1 ? '' : (tree.expressions[i] as Partial<Extract<ESTree.Node, { type: 'Literal' }>>).value;
return a + c.value.raw + (typeof v === 'string' ? v : '');
}, '');
}
@@ -72,44 +71,144 @@ function normalizeClassWalker(tree: estree.Node, stack: string | undefined): str
tree.type !== 'ChainExpression' &&
tree.type !== 'ConditionalExpression' &&
tree.type !== 'LogicalExpression' &&
tree.type !== 'MemberExpression') {
tree.type !== 'MemberExpression'
) {
console.error(stack ? `Unexpected node type: ${tree.type} (in ${stack})` : `Unexpected node type: ${tree.type}`);
}
return null;
}
export function normalizeClass(tree: estree.Node, stack?: string): string | null {
export function normalizeClass(tree: ESTree.Node, stack?: string): string | null {
const walked = normalizeClassWalker(tree, stack);
return walked && walked.replace(/^\s+|\s+(?=\s)|\s+$/g, '');
}
export function unwindCssModuleClassName(ast: estree.Node): void {
(walk as typeof estreeWalker.walk)(ast, {
enter(node, parent): void {
function getPropertyName(node: ESTree.Node, computed: boolean): string | null {
if (node.type === 'Identifier') return computed ? null : node.name;
if (node.type === 'Literal' && typeof node.value === 'string') return node.value;
return null;
}
function getMemberPropertyName(node: ESTree.MemberExpression['property'], computed: boolean): string | null {
if (node.type === 'Identifier') return computed ? null : node.name;
if (node.type === 'Literal' && typeof node.value === 'string') return node.value;
return null;
}
function findVariableDeclaration(program: ESTree.Program, name: string): ESTree.VariableDeclaration | null {
return program.body.find((x) => {
if (x.type !== 'VariableDeclaration') return false;
if (x.declarations.length !== 1) return false;
if (x.declarations[0].id.type !== 'Identifier') return false;
return x.declarations[0].id.name === name;
}) as ESTree.VariableDeclaration | null;
}
function resolveObjectExpression(program: ESTree.Program, tree: ESTree.Expression): ESTree.ObjectExpression | null {
if (tree.type === 'ObjectExpression') return tree;
if (tree.type !== 'Identifier') return null;
const declaration = findVariableDeclaration(program, tree.name);
if (declaration?.declarations[0].init?.type !== 'ObjectExpression') return null;
return declaration.declarations[0].init;
}
function resolveComponentOptions(program: ESTree.Program, tree: ESTree.Expression): ESTree.ObjectExpression | null {
const target = tree.type === 'Identifier'
? findVariableDeclaration(program, tree.name)?.declarations[0].init ?? null
: tree;
if (target?.type === 'ObjectExpression') return target;
if (target?.type !== 'CallExpression') return null;
if (target.arguments.length !== 1) return null;
if (target.arguments[0].type !== 'ObjectExpression') return null;
return target.arguments[0];
}
function resolveModuleTree(program: ESTree.Program, tree: ESTree.Expression): Map<string, string> | null {
const objectExpression = resolveObjectExpression(program, tree);
if (objectExpression === null) return null;
return new Map(objectExpression.properties.flatMap((property) => {
if (property.type !== 'Property') return [];
const actualKey = getPropertyName(property.key, property.computed);
if (actualKey === null) return [];
if (property.value.type === 'Literal') {
return typeof property.value.value === 'string' ? [[actualKey, property.value.value]] : [];
}
if (property.value.type === 'Identifier') {
const actualValue = findVariableDeclaration(program, property.value.name);
if (actualValue?.declarations[0].init?.type !== 'Literal') return [];
return typeof actualValue.declarations[0].init.value === 'string' ? [[actualKey, actualValue.declarations[0].init.value]] : [];
}
return [];
}));
}
function resolveModuleForest(program: ESTree.Program, tree: ESTree.Expression): Map<string, Map<string, string>> | null {
const objectExpression = resolveObjectExpression(program, tree);
if (objectExpression === null) return null;
return new Map(objectExpression.properties.flatMap((property) => {
if (property.type !== 'Property') return [];
const actualKey = getPropertyName(property.key, property.computed);
if (actualKey === null) return [];
const moduleTree = resolveModuleTree(program, property.value);
return moduleTree === null ? [] : [[actualKey, moduleTree]];
}));
}
function findRenderArrow(options: ESTree.ObjectExpression): Extract<ESTree.Node, { type: 'ArrowFunctionExpression' }> | null {
const setup = options.properties.find((x) => {
if (x.type !== 'Property') return false;
return getPropertyName(x.key, x.computed) === 'setup';
}) as Extract<ESTree.Node, { type: 'Property' }> | undefined;
if (setup?.value.type !== 'FunctionExpression' && setup?.value.type !== 'ArrowFunctionExpression') return null;
if (setup.value.body == null) return null;
if (setup.value.body.type !== 'BlockStatement') return null;
const render = setup.value.body.body.find((x) => x.type === 'ReturnStatement');
if (render?.type !== 'ReturnStatement') return null;
return render.argument?.type === 'ArrowFunctionExpression' ? render.argument : null;
}
function isCssModuleAccess(node: ESTree.Node, ctxName: string, key: string): node is Extract<ESTree.Node, { type: 'MemberExpression' }> {
if (node.type !== 'MemberExpression') return false;
if (node.object.type !== 'MemberExpression') return false;
if (node.object.object.type !== 'Identifier') return false;
if (node.object.object.name !== ctxName) return false;
return getMemberPropertyName(node.object.property, node.object.computed) === key;
}
function isCssModuleReference(node: ESTree.Node, ctxName: string, key: string): node is Extract<ESTree.Node, { type: 'MemberExpression' }> {
if (!isCssModuleAccess(node, ctxName, key)) return false;
return getMemberPropertyName(node.property, node.computed) !== null;
}
function isClassProperty(node: ESTree.Node | null): node is Extract<ESTree.Node, { type: 'Property' }> {
return node?.type === 'Property' && getPropertyName(node.key, node.computed) === 'class';
}
export function unwindCssModuleClassName(ast: ESTree.Node, magicString: RolldownMagicString): void {
(estreeWalker.walk as any)(ast, {
enter(node: ESTree.Node, parent: ESTree.Node | null): void {
//#region
if (parent?.type !== 'Program') return;
if (ast.type !== 'Program') return;
if (node.type !== 'VariableDeclaration') return;
if (node.declarations.length !== 1) return;
if (node.declarations[0].id.type !== 'Identifier') return;
const name = node.declarations[0].id.name;
if (node.declarations[0].init?.type !== 'CallExpression') return;
if (node.declarations[0].init.callee.type !== 'Identifier') return;
if (node.declarations[0].init.callee.name !== '_export_sfc') return;
if (node.declarations[0].init.arguments.length !== 2) return;
if (node.declarations[0].init.arguments[0].type !== 'Identifier') return;
const ident = node.declarations[0].init.arguments[0].name;
if (!ident.startsWith('_sfc_main')) return;
const componentNode = node.declarations[0].init.arguments[0];
if (componentNode.type !== 'Identifier' && componentNode.type !== 'CallExpression' && componentNode.type !== 'ObjectExpression') return;
if (node.declarations[0].init.arguments[1].type !== 'ArrayExpression') return;
if (node.declarations[0].init.arguments[1].elements.length === 0) return;
const __cssModulesIndex = node.declarations[0].init.arguments[1].elements.findIndex((x) => {
const cssModulesEntry = node.declarations[0].init.arguments[1].elements.find((x) => {
if (x?.type !== 'ArrayExpression') return false;
if (x.elements.length !== 2) return false;
if (x.elements[0]?.type !== 'Literal') return false;
if (x.elements[0].value !== '__cssModules') return false;
if (x.elements[1]?.type !== 'Identifier') return false;
return true;
});
if (!~__cssModulesIndex) return;
}) as ESTree.ArrayExpression | undefined;
const __cssModulesIndex = node.declarations[0].init.arguments[1].elements.indexOf(cssModulesEntry ?? null);
if (cssModulesEntry === undefined || __cssModulesIndex < 0) return;
/* This region assumeed that the entered node looks like the following code.
*
* ```ts
@@ -118,21 +217,10 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
*/
//#endregion
//#region
const cssModuleForestName = ((node.declarations[0].init.arguments[1].elements[__cssModulesIndex] as estree.ArrayExpression).elements[1] as estree.Identifier).name;
const cssModuleForestNode = parent.body.find((x) => {
if (x.type !== 'VariableDeclaration') return false;
if (x.declarations.length !== 1) return false;
if (x.declarations[0].id.type !== 'Identifier') return false;
if (x.declarations[0].id.name !== cssModuleForestName) return false;
if (x.declarations[0].init?.type !== 'ObjectExpression') return false;
return true;
}) as unknown as estree.VariableDeclaration;
const moduleForest = new Map((cssModuleForestNode.declarations[0].init as estree.ObjectExpression).properties.flatMap((property) => {
if (property.type !== 'Property') return [];
if (property.key.type !== 'Literal') return [];
if (property.value.type !== 'Identifier') return [];
return [[property.key.value as string, property.value.name as string]];
}));
const cssModuleForest = cssModulesEntry.elements[1];
if (cssModuleForest?.type !== 'Identifier' && cssModuleForest?.type !== 'ObjectExpression') return;
const moduleForest = resolveModuleForest(ast, cssModuleForest);
if (moduleForest === null) return;
/* This region collected a VariableDeclaration node in the module that looks like the following code.
*
* ```ts
@@ -143,35 +231,13 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
*/
//#endregion
//#region
const sfcMain = parent.body.find((x) => {
if (x.type !== 'VariableDeclaration') return false;
if (x.declarations.length !== 1) return false;
if (x.declarations[0].id.type !== 'Identifier') return false;
if (x.declarations[0].id.name !== ident) return false;
return true;
}) as unknown as estree.VariableDeclaration;
if (sfcMain.declarations[0].init?.type !== 'CallExpression') return;
if (sfcMain.declarations[0].init.callee.type !== 'Identifier') return;
if (sfcMain.declarations[0].init.callee.name !== 'defineComponent') return;
if (sfcMain.declarations[0].init.arguments.length !== 1) return;
if (sfcMain.declarations[0].init.arguments[0].type !== 'ObjectExpression') return;
const setup = sfcMain.declarations[0].init.arguments[0].properties.find((x) => {
if (x.type !== 'Property') return false;
if (x.key.type !== 'Identifier') return false;
if (x.key.name !== 'setup') return false;
return true;
}) as unknown as estree.Property;
if (setup.value.type !== 'FunctionExpression') return;
const render = setup.value.body.body.find((x) => {
if (x.type !== 'ReturnStatement') return false;
return true;
}) as unknown as estree.ReturnStatement;
if (render.argument?.type !== 'ArrowFunctionExpression') return;
if (render.argument.params.length !== 2) return;
const ctx = render.argument.params[0];
const options = resolveComponentOptions(ast, componentNode);
if (options === null) return;
const render = findRenderArrow(options);
if (render === null) return;
if (render.params.length !== 2) return;
const ctx = render.params[0];
if (ctx.type !== 'Identifier') return;
if (ctx.name !== '_ctx') return;
if (render.argument.body.type !== 'BlockStatement') return;
/* This region assumed that `sfcMain` looks like the following code.
*
* ```ts
@@ -186,33 +252,8 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
* ```
*/
//#endregion
for (const [key, value] of moduleForest) {
for (const [key, moduleTree] of moduleForest) {
//#region
const cssModuleTreeNode = parent.body.find((x) => {
if (x.type !== 'VariableDeclaration') return false;
if (x.declarations.length !== 1) return false;
if (x.declarations[0].id.type !== 'Identifier') return false;
if (x.declarations[0].id.name !== value) return false;
return true;
}) as unknown as estree.VariableDeclaration;
if (cssModuleTreeNode.declarations[0].init?.type !== 'ObjectExpression') return;
const moduleTree = new Map(cssModuleTreeNode.declarations[0].init.properties.flatMap((property) => {
if (property.type !== 'Property') return [];
const actualKey = property.key.type === 'Identifier' ? property.key.name : property.key.type === 'Literal' ? property.key.value : null;
if (typeof actualKey !== 'string') return [];
if (property.value.type === 'Literal') return [[actualKey, property.value.value as string]];
if (property.value.type !== 'Identifier') return [];
const labelledValue = property.value.name;
const actualValue = parent.body.find((x) => {
if (x.type !== 'VariableDeclaration') return false;
if (x.declarations.length !== 1) return false;
if (x.declarations[0].id.type !== 'Identifier') return false;
if (x.declarations[0].id.name !== labelledValue) return false;
return true;
}) as unknown as estree.VariableDeclaration;
if (actualValue.declarations[0].init?.type !== 'Literal') return [];
return [[actualKey, actualValue.declarations[0].init.value as string]];
}));
/* This region collected VariableDeclaration nodes in the module that looks like the following code.
*
* ```ts
@@ -226,17 +267,14 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
*/
//#endregion
//#region
(walk as typeof estreeWalker.walk)(render.argument.body, {
enter(childNode) {
if (childNode.type !== 'MemberExpression') return;
if (childNode.object.type !== 'MemberExpression') return;
if (childNode.object.object.type !== 'Identifier') return;
if (childNode.object.object.name !== ctx.name) return;
if (childNode.object.property.type !== 'Identifier') return;
if (childNode.object.property.name !== key) return;
if (childNode.property.type !== 'Identifier') return;
const actualValue = moduleTree.get(childNode.property.name);
(estreeWalker.walk as any)(render.body, {
enter(childNode: ESTree.Node) {
if (!isCssModuleReference(childNode, ctx.name, key)) return;
const actualKey = getMemberPropertyName(childNode.property, childNode.computed);
if (actualKey === null) return;
const actualValue = moduleTree.get(actualKey);
if (actualValue === undefined) return;
magicString.overwrite(childNode.start, childNode.end, JSON.stringify(actualValue));
this.replace({
type: 'Literal',
value: actualValue,
@@ -276,20 +314,13 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
*/
//#endregion
//#region
(walk as typeof estreeWalker.walk)(render.argument.body, {
enter(childNode) {
if (childNode.type !== 'MemberExpression') return;
if (childNode.object.type !== 'MemberExpression') return;
if (childNode.object.object.type !== 'Identifier') return;
if (childNode.object.object.name !== ctx.name) return;
if (childNode.object.property.type !== 'Identifier') return;
if (childNode.object.property.name !== key) return;
if (childNode.property.type !== 'Identifier') return;
console.error(`Undefined style detected: ${key}.${childNode.property.name} (in ${name})`);
this.replace({
type: 'Identifier',
name: 'undefined',
});
(estreeWalker.walk as any)(render.body, {
enter(childNode: ESTree.Node) {
if (!isCssModuleReference(childNode, ctx.name, key)) return;
const actualKey = getMemberPropertyName(childNode.property, childNode.computed);
if (actualKey === null) return;
console.error(`Undefined style detected: ${key}.${actualKey} (in ${name})`);
magicString.overwrite(childNode.start, childNode.end, 'undefined');
},
});
/* This region replaced the reference identifier of missing class names in the render function with `undefined`, as in the following code.
@@ -300,7 +331,7 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
* ...
* return (_ctx, _cache) => {
* ...
* return openBlock(), createElementBlock("div", {
* return openBlock(), createElementBlock('div', {
* class: normalizeClass(_ctx.$style.hoge),
* }, null);
* };
@@ -316,7 +347,7 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
* ...
* return (_ctx, _cache) => {
* ...
* return openBlock(), createElementBlock("div", {
* return openBlock(), createElementBlock('div', {
* class: normalizeClass(undefined),
* }, null);
* };
@@ -326,18 +357,15 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
*/
//#endregion
//#region
(walk as typeof estreeWalker.walk)(render.argument.body, {
enter(childNode) {
(estreeWalker.walk as any)(render.body, {
enter(childNode: ESTree.Node, childParent: ESTree.Node | null) {
if (childNode.type !== 'CallExpression') return;
if (childNode.callee.type !== 'Identifier') return;
if (childNode.callee.name !== 'normalizeClass') return;
if (childNode.arguments.length !== 1) return;
if (childNode.callee.type === 'Identifier' && childNode.callee.name !== 'normalizeClass' && !isClassProperty(childParent)) return;
if (childNode.callee.type !== 'Identifier' && !isClassProperty(childParent)) return;
const normalized = normalizeClass(childNode.arguments[0], name);
if (normalized === null) return;
this.replace({
type: 'Literal',
value: normalized,
});
magicString.overwrite(childNode.start, childNode.end, JSON.stringify(normalized));
},
});
/* This region compiled the `normalizeClass` call into a pseudo-AOT compilation, as in the following code.
@@ -374,19 +402,34 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
*/
//#endregion
}
//#region
if (node.declarations[0].init.arguments[1].elements.length === 1) {
(walk as typeof estreeWalker.walk)(ast, {
enter(childNode) {
if (childNode.type !== 'Identifier') return;
if (childNode.name !== ident) return;
this.replace({
type: 'Identifier',
name: node.declarations[0].id.name,
});
const hasRemainingCssModuleReference = Array.from(moduleForest.keys()).some((key) => {
let found = false;
(estreeWalker.walk as any)(render.body, {
enter(childNode: ESTree.Node) {
if (!isCssModuleAccess(childNode, ctx.name, key)) return;
found = true;
this.skip();
},
});
this.remove();
return found;
});
if (hasRemainingCssModuleReference) return;
//#region
if (node.declarations[0].init.arguments[1].elements.length === 1) {
if (componentNode.type === 'Identifier') {
(estreeWalker.walk as any)(ast, {
enter(childNode: ESTree.Node) {
if (childNode.type !== 'Identifier') return;
if (childNode.name !== componentNode.name) return;
magicString.overwrite(childNode.start, childNode.end, name);
},
});
magicString.remove(node.start, node.end);
} else {
const removeStart = cssModulesEntry.start;
const removeEnd = node.declarations[0].init.arguments[1].end - 1;
magicString.remove(removeStart, removeEnd);
}
/* NOTE: The above logic is valid as long as the following two conditions are met.
*
* - the uniqueness of `ident` is kept throughout the module
@@ -411,31 +454,10 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
});
*/
} else {
this.replace({
type: 'VariableDeclaration',
declarations: [{
type: 'VariableDeclarator',
id: {
type: 'Identifier',
name: node.declarations[0].id.name,
},
init: {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: '_export_sfc',
},
arguments: [{
type: 'Identifier',
name: ident,
}, {
type: 'ArrayExpression',
elements: node.declarations[0].init.arguments[1].elements.slice(0, __cssModulesIndex).concat(node.declarations[0].init.arguments[1].elements.slice(__cssModulesIndex + 1)),
}],
},
}],
kind: 'const',
});
const nextElement = node.declarations[0].init.arguments[1].elements[__cssModulesIndex + 1];
const removeStart = node.declarations[0].init.arguments[1].elements[__cssModulesIndex]!.start;
const removeEnd = nextElement ? nextElement.start : node.declarations[0].init.arguments[1].end - 1;
magicString.remove(removeStart, removeEnd);
}
/* This region removed the `__cssModules` reference from the second argument of `_export_sfc`, as in the following code.
*
@@ -474,10 +496,11 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
export default function pluginUnwindCssModuleClassName(): Plugin {
return {
name: 'UnwindCssModuleClassName',
renderChunk(code): { code: string } {
const ast = this.parse(code) as unknown as estree.Node;
unwindCssModuleClassName(ast);
return { code: generate(ast) };
renderChunk(code, _chunk, _options, meta) {
const ast = ('ast' in meta ? meta.ast ?? this.parse(code) : this.parse(code)) as ESTree.Program;
const magicString = meta.magicString ?? new RolldownMagicString(code);
unwindCssModuleClassName(ast, magicString);
return magicString;
},
};
}

View File

@@ -13,11 +13,12 @@ import {
type LogOptions,
normalizePath,
type Plugin,
type PluginOption
type PluginOption,
} from 'vite';
import fs from 'node:fs';
import JSON5 from 'json5';
import MagicString, { SourceMap } from 'magic-string';
import { RolldownMagicString } from 'rolldown';
import type { TransformResult } from 'rolldown';
import path from 'node:path'
import { hash, toBase62 } from '../vite.config';
import { minimatch } from 'minimatch';
@@ -63,7 +64,7 @@ interface MarkerRelation {
let logger = {
info: (msg: string, options?: LogOptions) => { },
warn: (msg: string, options?: LogOptions) => { },
error: (msg: string, options?: LogErrorOptions | unknown) => { },
error: (msg: string, options?: LogErrorOptions) => { },
};
let loggerInitialized = false;
@@ -460,9 +461,18 @@ function propertyAccessProxy(path: string[]): AccessProxy {
const i18nProxy = propertyAccessProxy(['i18n']);
export function collectFileMarkers(id: string, code: string): SearchIndexItem[] {
export function collectFileMarkers(id: string, code: string | RolldownMagicString | undefined): SearchIndexItem[] {
try {
const { descriptor, errors } = vueSfcParse(code, {
let codeStr: string;
if (typeof code === 'string') {
codeStr = code;
} else if (code != null) {
codeStr = code.toString();
} else {
throw new Error(`Code is undefined for file ${id}`);
}
const { descriptor, errors } = vueSfcParse(codeStr, {
filename: id,
});
@@ -473,7 +483,8 @@ export function collectFileMarkers(id: string, code: string): SearchIndexItem[]
return extractUsageInfoFromTemplateAst(descriptor.template?.ast, id);
} catch (error) {
logger.error(`Error analyzing file ${id}:`, error);
let _error = error instanceof Error ? error : new Error(String(error));
logger.error(`Error analyzing file ${id}:`, { error: _error });
}
return [];
@@ -481,10 +492,7 @@ export function collectFileMarkers(id: string, code: string): SearchIndexItem[]
// endregion
type TransformedCode = {
code: string,
map: SourceMap,
};
type TransformedCode = Exclude<TransformResult, string>;
export class MarkerIdAssigner {
// key: file id
@@ -509,13 +517,12 @@ export class MarkerIdAssigner {
}
#processImpl(id: string, code: string): TransformedCode {
const s = new MagicString(code); // magic-string のインスタンスを作成
const s = new RolldownMagicString(code); // magic-string のインスタンスを作成
const parsed = vueSfcParse(code, { filename: id });
if (!parsed.descriptor.template) {
return {
code,
map: s.generateMap({ source: id, includeContent: true }),
code, // テンプレートがない場合は元のコードを返す
};
}
const ast = parsed.descriptor.template.ast; // テンプレート AST を取得
@@ -523,8 +530,7 @@ export class MarkerIdAssigner {
if (!ast) {
return {
code: s.toString(), // 変更後のコードを返す
map: s.generateMap({ source: id, includeContent: true }), // ソースマップも生成 (sourceMap: true が必要)
code,
};
}
@@ -611,7 +617,6 @@ export class MarkerIdAssigner {
return {
code: s.toString(), // 変更後のコードを返す
map: s.generateMap({ source: id, includeContent: true }), // ソースマップも生成 (sourceMap: true が必要)
};
}
@@ -642,7 +647,7 @@ export class MarkerIdAssigner {
}
}
// Rollup プラグインとして export
// Vite プラグインとして export
export default function pluginCreateSearchIndex(options: Options): PluginOption {
const assigner = new MarkerIdAssigner();
return [

View File

@@ -1,7 +1,10 @@
// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import JSON5 from 'json5';
import { Plugin } from 'rollup';
import { Plugin } from 'vite';
import { createFilter, dataToEsm } from '@rollup/pluginutils';
import { RollupJsonOptions } from '@rollup/plugin-json';

View File

@@ -3,16 +3,16 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import path from 'node:path'
import path from 'node:path';
import locales from 'i18n';
import type { Plugin } from 'vite';
const localesDir = path.resolve(__dirname, '../../../locales')
/**
* 外部ファイルを監視し、必要に応じてwebSocketでメッセージを送るViteプラグイン
* @returns {import('vite').Plugin}
*/
export default function pluginWatchLocales() {
export default function pluginWatchLocales(): Plugin {
return {
name: 'watch-locales',

View File

@@ -25,10 +25,7 @@
"@github/webauthn-json": "2.1.1",
"@mcaptcha/core-glue": "0.1.0-alpha-5",
"@misskey-dev/browser-image-resizer": "2024.1.0",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.3",
"@rollup/pluginutils": "5.3.0",
"@sentry/vue": "10.43.0",
"@sentry/vue": "10.40.0",
"@syuilo/aiscript": "1.2.1",
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
"@twemoji/parser": "16.0.0",
@@ -43,7 +40,7 @@
"chartjs-chart-matrix": "3.0.0",
"chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.2.0",
"chromatic": "15.2.0",
"chromatic": "15.3.1",
"compare-versions": "6.1.1",
"cropperjs": "2.1.0",
"date-fns": "4.1.0",
@@ -59,7 +56,7 @@
"is-file-animated": "1.0.2",
"json5": "2.2.3",
"matter-js": "0.20.0",
"mediabunny": "1.39.2",
"mediabunny": "1.40.1",
"mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
@@ -68,24 +65,23 @@
"punycode.js": "2.3.1",
"qr-code-styling": "1.9.2",
"qr-scanner": "1.4.2",
"rollup": "4.59.0",
"sanitize-html": "2.17.1",
"sass": "1.98.0",
"shiki": "3.23.0",
"textarea-caret": "3.1.0",
"three": "0.183.2",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
"v-code-diff": "1.13.1",
"vite": "7.3.1",
"vue": "3.5.30",
"wanakana": "5.3.1"
},
"devDependencies": {
"@misskey-dev/summaly": "5.2.5",
"@rollup/plugin-json": "6.1.0",
"@rollup/pluginutils": "5.3.0",
"@storybook/addon-essentials": "8.6.18",
"@storybook/addon-interactions": "8.6.18",
"@storybook/addon-links": "10.2.17",
"@storybook/addon-links": "10.3.3",
"@storybook/addon-mdx-gfm": "8.6.18",
"@storybook/addon-storysource": "8.6.18",
"@storybook/blocks": "8.6.18",
@@ -93,13 +89,13 @@
"@storybook/core-events": "8.6.18",
"@storybook/manager-api": "8.6.18",
"@storybook/preview-api": "8.6.18",
"@storybook/react": "10.2.17",
"@storybook/react-vite": "10.2.17",
"@storybook/react": "10.3.3",
"@storybook/react-vite": "10.3.3",
"@storybook/test": "8.6.18",
"@storybook/theming": "8.6.18",
"@storybook/types": "8.6.18",
"@storybook/vue3": "10.2.17",
"@storybook/vue3-vite": "10.2.17",
"@storybook/vue3": "10.3.3",
"@storybook/vue3-vite": "10.3.3",
"@tabler/icons-webfont": "3.35.0",
"@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0",
@@ -114,39 +110,41 @@
"@types/textarea-caret": "3.0.4",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@vitest/coverage-v8": "4.1.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"@vitest/coverage-v8": "4.1.1",
"@vue/compiler-core": "3.5.30",
"acorn": "8.16.0",
"astring": "1.9.0",
"cross-env": "10.1.0",
"cypress": "15.11.0",
"cypress": "15.13.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.8.0",
"estree-walker": "3.0.3",
"happy-dom": "20.8.4",
"happy-dom": "20.8.8",
"intersection-observer": "0.12.2",
"magic-string": "0.30.21",
"micromatch": "4.0.8",
"minimatch": "10.2.4",
"msw": "2.12.10",
"msw": "2.12.14",
"msw-storybook-addon": "2.0.6",
"nodemon": "3.1.14",
"prettier": "3.8.1",
"react": "19.2.4",
"react-dom": "19.2.4",
"rolldown": "1.0.0-rc.11",
"sass-embedded": "1.98.0",
"seedrandom": "3.0.5",
"start-server-and-test": "2.1.5",
"storybook": "10.2.17",
"storybook": "10.3.3",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.21.0",
"vite-plugin-glsl": "1.5.5",
"vite": "8.0.2",
"vite-plugin-glsl": "1.5.6",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "4.1.0",
"vitest": "4.1.1",
"vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "3.2.5",
"vue-component-type-helpers": "3.2.6",
"vue-eslint-parser": "10.4.0",
"vue-tsc": "3.2.5"
"vue-tsc": "3.2.6"
}
}

View File

@@ -1,7 +1,7 @@
import path from 'path';
import pluginReplace from '@rollup/plugin-replace';
import pluginVue from '@vitejs/plugin-vue';
import pluginGlsl from 'vite-plugin-glsl';
import { replacePlugin } from 'rolldown/plugins';
import type { UserConfig } from 'vite';
import { defineConfig } from 'vite';
import * as yaml from 'js-yaml';
@@ -11,13 +11,13 @@ import locales from 'i18n';
import meta from '../../package.json';
import packageInfo from './package.json' with { type: 'json' };
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
import pluginJson5 from './vite.json5.js';
import pluginJson5 from './lib/vite-plugin-json5.js';
import type { Options as SearchIndexOptions } from './lib/vite-plugin-create-search-index.js';
import pluginCreateSearchIndex from './lib/vite-plugin-create-search-index.js';
import pluginWatchLocales from './lib/vite-plugin-watch-locales.js';
import { pluginRemoveUnrefI18n } from '../frontend-builder/rollup-plugin-remove-unref-i18n.js';
const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null;
const url = process.env.NODE_ENV === 'development' ? (yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')) as any).url : null;
const host = url ? (new URL(url)).hostname : undefined;
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
@@ -121,11 +121,10 @@ export function getConfig(): UserConfig {
pluginGlsl({ minify: true }),
...process.env.NODE_ENV === 'production'
? [
pluginReplace({
replacePlugin({
'isChromatic()': JSON.stringify(false),
}, {
preventAssignment: true,
values: {
'isChromatic()': JSON.stringify(false),
},
}),
]
: [],
@@ -154,11 +153,6 @@ export function getConfig(): UserConfig {
}
},
},
preprocessorOptions: {
scss: {
api: 'modern-compiler',
},
},
},
define: {
@@ -178,7 +172,10 @@ export function getConfig(): UserConfig {
'safari16',
],
manifest: 'manifest.json',
rollupOptions: {
rolldownOptions: {
experimental: {
nativeMagicString: true,
},
input: {
i18n: './src/i18n.ts',
entry: './src/_boot_.ts',
@@ -186,11 +183,18 @@ export function getConfig(): UserConfig {
external: externalPackages.map(p => p.match),
preserveEntrySignatures: 'allow-extension',
output: {
manualChunks: {
vue: ['vue'],
photoswipe: ['photoswipe', 'photoswipe/lightbox', 'photoswipe/style.css'],
// dependencies of i18n.ts
'config': ['@@/js/config.js'],
codeSplitting: {
groups: [{
name: 'vue',
test: /node_modules[\\/]vue/,
}, {
name: 'photoswipe',
test: /node_modules[\\/]photoswipe/,
}, {
// dependencies of i18n.ts
name: 'config',
test: /@@[\\/]js[\\/]config\.js/,
}],
},
entryFileNames: `scripts/${localesHash}-[hash:8].js`,
chunkFileNames: `scripts/${localesHash}-[hash:8].js`,

View File

@@ -30,8 +30,8 @@
"devDependencies": {
"@types/js-yaml": "4.0.9",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"chokidar": "5.0.0",
"esbuild": "0.27.4",
"execa": "9.6.1",

View File

@@ -13,12 +13,12 @@
"devDependencies": {
"@types/node": "24.12.0",
"@types/wawoff2": "1.0.2",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0"
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2"
},
"dependencies": {
"@tabler/icons-webfont": "3.35.0",
"harfbuzzjs": "0.10.0",
"harfbuzzjs": "0.10.1",
"tsx": "4.21.0",
"wawoff2": "2.0.1"
},

View File

@@ -27,8 +27,8 @@
"@types/matter-js": "0.20.2",
"@types/node": "24.12.0",
"@types/seedrandom": "3.0.8",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"esbuild": "0.27.4",
"execa": "9.6.1",
"nodemon": "3.1.14"

View File

@@ -9,8 +9,8 @@
"devDependencies": {
"@readme/openapi-parser": "5.5.0",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"openapi-types": "12.1.3",
"openapi-typescript": "7.13.0",
"ts-case-convert": "2.1.0",

View File

@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2026.3.2-beta.0",
"version": "2026.3.2",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
@@ -39,15 +39,15 @@
"devDependencies": {
"@microsoft/api-extractor": "7.57.7",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@vitest/coverage-v8": "4.1.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"@vitest/coverage-v8": "4.1.1",
"esbuild": "0.27.4",
"execa": "9.6.1",
"ncp": "2.0.0",
"nodemon": "3.1.14",
"tsd": "0.33.0",
"vitest": "4.1.0",
"vitest": "4.1.1",
"vitest-websocket-mock": "0.5.0"
},
"files": [

View File

@@ -25,8 +25,8 @@
},
"devDependencies": {
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@typescript-eslint/eslint-plugin": "8.57.2",
"@typescript-eslint/parser": "8.57.2",
"esbuild": "0.27.4",
"execa": "9.6.1",
"nodemon": "3.1.14"

View File

@@ -15,7 +15,7 @@
"misskey-js": "workspace:*"
},
"devDependencies": {
"@typescript-eslint/parser": "8.57.0",
"@typescript-eslint/parser": "8.57.2",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
"eslint-plugin-import": "2.32.0",
"nodemon": "3.1.14"

3451
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -36,5 +36,3 @@ minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack
minimumReleaseAgeExclude:
- '@syuilo/aiscript'
- '@babylonjs/*'
- 'tar' # 脆弱性対応 そのうち消す
- 'fastify' # 脆弱性対応 そのうち消す

View File

@@ -11,7 +11,7 @@
"devDependencies": {
"@types/mdast": "4.0.4",
"@types/node": "24.12.0",
"@vitest/coverage-v8": "4.1.0",
"@vitest/coverage-v8": "4.1.1",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
@@ -19,6 +19,6 @@
"unified": "11.0.5",
"vite": "7.3.1",
"vite-node": "5.3.0",
"vitest": "4.1.0"
"vitest": "4.1.1"
}
}