Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/core/src/server/transform/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import fs from 'fs';
import { transformJsx } from './transform-jsx';
import { transformSvelte } from './transform-svelte';
import { transformVue } from './transform-vue';
import { transformVue, transformVueHtml } from './transform-vue';
import { EscapeTags, PathType, isIgnoredFile } from '../../shared';
import { getRelativeOrAbsolutePath } from '../server';

type FileType = 'vue' | 'jsx' | 'svelte' | unknown;
type FileType = 'vue' | 'vue-html' | 'jsx' | 'svelte' | unknown;

type TransformCodeParams = {
content: string;
Expand Down Expand Up @@ -49,6 +49,8 @@ export function transformCode(params: TransformCodeParams) {
try {
if (fileType === 'vue') {
return transformVue(content, filePath, finalEscapeTags);
} else if (fileType === 'vue-html') {
return transformVueHtml(content, filePath, finalEscapeTags);
} else if (fileType === 'jsx') {
return transformJsx(content, filePath, finalEscapeTags);
} else if (fileType === 'svelte') {
Expand Down
24 changes: 24 additions & 0 deletions packages/core/src/server/transform/transform-vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,27 @@ function transformVueTemplate(
],
});
}

/**
* Transform standalone HTML template files used as Vue templates.
* These are .html files that contain Vue template syntax and are loaded via
* require('./xxx.html') rather than through vue-loader's SFC mechanism.
*
* Unlike transformVue which handles full .vue SFC files, this function
* directly parses the HTML content as a Vue template fragment.
*/
export function transformVueHtml(
content: string,
filePath: string,
escapeTags: EscapeTags
) {
const s = new MagicString(content);

const ast = parse(content, {
comments: true,
});

transformVueTemplate(ast, filePath, escapeTags, s);

return s.toString();
}
6 changes: 3 additions & 3 deletions packages/core/src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,16 +366,16 @@ export function isIgnoredFile({
fileType,
}: {
content: string;
fileType: 'vue' | 'jsx' | 'svelte' | unknown;
fileType: 'vue' | 'vue-html' | 'jsx' | 'svelte' | unknown;
}): boolean {
if (!content) {
return false;
}
const trimmed = content.trimStart();
const directives = ['code-inspector-disable', 'code-inspector-ignore'];

// Vue / Svelte - check HTML comments
if (fileType === 'vue' || fileType === 'svelte') {
// Vue / Vue HTML / Svelte - check HTML comments
if (fileType === 'vue' || fileType === 'vue-html' || fileType === 'svelte') {
if (trimmed.startsWith('<!--')) {
const endIndex = trimmed.indexOf('-->');
if (endIndex !== -1) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/types/server/transform/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EscapeTags, PathType } from '../../shared';
type FileType = 'vue' | 'jsx' | 'svelte' | unknown;
type FileType = 'vue' | 'vue-html' | 'jsx' | 'svelte' | unknown;
type TransformCodeParams = {
content: string;
filePath: string;
Expand Down
9 changes: 9 additions & 0 deletions packages/core/types/server/transform/transform-vue.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
import { EscapeTags } from '../../shared';
export declare function transformVue(content: string, filePath: string, escapeTags: EscapeTags): string;
/**
* Transform standalone HTML template files used as Vue templates.
* These are .html files that contain Vue template syntax and are loaded via
* require('./xxx.html') rather than through vue-loader's SFC mechanism.
*
* Unlike transformVue which handles full .vue SFC files, this function
* directly parses the HTML content as a Vue template fragment.
*/
export declare function transformVueHtml(content: string, filePath: string, escapeTags: EscapeTags): string;
2 changes: 1 addition & 1 deletion packages/core/types/shared/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,6 @@ export declare function hasWritePermission(filePath: string): boolean;
*/
export declare function isIgnoredFile({ content, fileType, }: {
content: string;
fileType: 'vue' | 'jsx' | 'svelte' | unknown;
fileType: 'vue' | 'vue-html' | 'jsx' | 'svelte' | unknown;
}): boolean;
export {};
25 changes: 24 additions & 1 deletion packages/webpack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ const applyLoader = (options: LoaderOptions, compiler: any) => {
const module = _compiler?.options?.module;
/* v8 ignore next -- fallback for legacy webpack versions with module.loaders */
const rules = module?.rules || module?.loaders || [];
// Determine the file match pattern, supporting both default and user-configured patterns
const matchPattern = options.match ?? /\.(vue|jsx|tsx|js|ts|mjs|mts|svelte)$/;
// Check if user's match pattern includes .html
const matchIncludesHtml = matchPattern instanceof RegExp && matchPattern.test('.html');
Comment on lines +40 to +43
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Html detection uses test('.html') 🐞 Bug ⛯ Reliability

The code decides whether .html is included by running RegExp.test('.html'), which is unreliable
for path-scoped regexes (e.g. /src\/.*\.html$/) and can prevent enabling the standalone html rule
and/or the loader’s standalone branch. This can make the new feature silently not work for common
match patterns.
Agent Prompt
## Issue description
Determining whether HTML is enabled via `RegExp.test('.html')` is brittle for path-scoped `options.match` patterns and can disable the feature unexpectedly.

## Issue Context
Other bundlers in this repo apply `options.match` to the real file path. Webpack path matching should behave similarly.

## Fix Focus Areas
- packages/webpack/src/index.ts[40-44]
- packages/webpack/src/loader.ts[79-94]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


rules.push(
{
test: options.match ?? /\.html$/,
Expand All @@ -49,8 +54,26 @@ const applyLoader = (options: LoaderOptions, compiler: any) => {
],
...(options.enforcePre === false ? {} : { enforce: 'pre' }),
},
// If user's match pattern includes .html, add a separate rule for standalone .html files
// These are HTML template files used as Vue templates via require('./xxx.html')
...(matchIncludesHtml
? [
{
test: /\.html$/,
use: [
{
loader: path.resolve(compatibleDirname, `./loader.js`),
options,
},
],
...(options.enforcePre === false ? {} : { enforce: 'pre' }),
},
]
: []),
{
test: /\.(vue|jsx|tsx|js|ts|mjs|mts|svelte)$/,
test: matchIncludesHtml
? /\.(vue|jsx|tsx|js|ts|mjs|mts|svelte)$/
: matchPattern,
Comment on lines +74 to +76
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Match overridden when html 🐞 Bug ✓ Correctness

When options.match includes .html, the webpack plugin replaces the user-provided match regex
with a hard-coded default for the main rule, which can silently stop transforming user-intended file
types (or start transforming unintended ones). This breaks the documented contract that match
controls which files participate in transformation.
Agent Prompt
## Issue description
When `options.match` includes `.html`, the webpack rules currently replace the user's `match` regex with a fixed default regex for the main transform rule. This breaks user configuration and can skip intended files.

## Issue Context
`CodeOptions.match` is documented as the controlling filter for which files participate in transformation.

## Fix Focus Areas
- packages/webpack/src/index.ts[40-84]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

use: [
{
loader: path.resolve(compatibleDirname, `./loader.js`),
Expand Down
12 changes: 10 additions & 2 deletions packages/webpack/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,19 @@ export default async function WebpackCodeInspectorLoader(content: string) {
filePath.endsWith('.html') &&
params.get('type') === 'template' &&
params.has('vue');
if (isVue || isHtmlVue) {
// Standalone HTML template files (used as Vue templates via require('./xxx.html'))
// These are HTML files that contain Vue template syntax but are not loaded through
// vue-loader's ?vue query mechanism
const isStandaloneHtmlTemplate =
filePath.endsWith('.html') &&
!params.has('vue') &&
options.match instanceof RegExp &&
options.match.test('.html');
if (isVue || isHtmlVue || isStandaloneHtmlTemplate) {
return transformCode({
content,
filePath,
fileType: 'vue',
fileType: isStandaloneHtmlTemplate ? 'vue-html' : 'vue',
escapeTags,
pathType: options.pathType,
});
Expand Down