Skip to content
Merged
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
61 changes: 59 additions & 2 deletions packages/vite/hmr/server/websocket-module-bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,61 @@ function collectTopLevelReExportSources(code: string): Array<{ start: number; en
}
}

function collectReExportAndDynamicImportSources(code: string): Array<{ start: number; end: number; source: string }> {
let ast: any;
try {
ast = babelParse(code, {
sourceType: 'module',
plugins: [...MODULE_IMPORT_ANALYSIS_PLUGINS],
});
} catch {
return [];
}
const sources: Array<{ start: number; end: number; source: string }> = [];
const add = (literal: any) => {
if (literal?.type === 'StringLiteral' && typeof literal.start === 'number' && typeof literal.end === 'number') {
sources.push({ start: literal.start, end: literal.end, source: literal.value });
}
};
const visit = (node: any) => {
if (!node || typeof node.type !== 'string') {
return;
}
if ((node.type === 'ExportAllDeclaration' || node.type === 'ExportNamedDeclaration') && node.exportKind !== 'type') {
add(node.source);
} else if (node.type === 'ImportExpression') {
add(node.source);
} else if (node.type === 'CallExpression' && node.callee?.type === 'Import') {
add(node.arguments?.[0]);
}
for (const key of Object.keys(node)) {
if (key === 'loc' || key === 'leadingComments' || key === 'trailingComments' || key === 'innerComments') {
continue;
}
const value = node[key];
if (Array.isArray(value)) {
value.forEach(visit);
} else if (value && typeof value === 'object') {
visit(value);
}
}
};
visit(ast?.program);
return sources;
}

function rewritePreservedReExportAndDynamicImportSpecifiers(code: string, options: EnsureNativeScriptModuleBindingsOptions): string {
const records = collectReExportAndDynamicImportSources(code);
for (const record of records.sort((left, right) => right.start - left.start)) {
const specifier = record.source.replace(PAT.QUERY_PATTERN, '');
const preserved = getPreservedImportSpecifier(specifier, options);
if (preserved && preserved !== specifier) {
code = code.slice(0, record.start) + JSON.stringify(preserved) + code.slice(record.end);
}
}
return code;
}

/**
* The worker serve's URL for a node_modules module Vite already resolved
* (`/node_modules/<pkg>/<file>`, `/@fs/.../node_modules/<pkg>/<file>`), or
Expand Down Expand Up @@ -174,6 +229,8 @@ function stripTopLevelImportRecords(code: string, records: TopLevelImportRecord[
export function ensureNativeScriptModuleBindings(code: string, options?: EnsureNativeScriptModuleBindingsOptions): string {
if (options?.vendorImportsAsHttp) {
code = rewriteWorkerReExportSpecifiers(code);
} else if (options?.resolvedSpecifierOverrides?.size) {
code = rewritePreservedReExportAndDynamicImportSpecifiers(code, options);
}
const importRecords = collectTopLevelImportRecords(code);
if (!importRecords.length) {
Expand Down Expand Up @@ -577,9 +634,9 @@ function buildResolvedRuntimePluginSpecifierOverrides(source: string, projectRoo
const overrides = new Map<string, string>();
const extracted = astExtractImportsAndStripTypes(source);
const mixedRuntimePluginRootPackages = collectMixedRuntimePluginRootPackages(source, projectRoot, sourceFilePath);
const importSpecifiers = extracted.imports.map((importCode) => importCode.match(/from\s+["']([^"']+)["']/)?.[1] || importCode.match(/^\s*import\s+["']([^"']+)["']/)?.[1] || null);

for (const importCode of extracted.imports) {
const sourceSpecifier = importCode.match(/from\s+["']([^"']+)["']/)?.[1] || importCode.match(/^\s*import\s+["']([^"']+)["']/)?.[1] || null;
for (const sourceSpecifier of [...importSpecifiers, ...collectReExportAndDynamicImportSources(source).map((record) => record.source)]) {
const normalizedSpecifier = sourceSpecifier ? normalizeRuntimePluginSourceSpecifier(sourceSpecifier, sourceFilePath, projectRoot) : null;
if (normalizedSpecifier) {
const { packageName } = resolveNodeModulesPackageBoundary(normalizedSpecifier, projectRoot);
Expand Down
27 changes: 27 additions & 0 deletions packages/vite/hmr/server/websocket-source-intent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,31 @@ describe('processCodeForDevice source import intent', () => {
expect(rewritten).toContain('from "http://localhost:5173/ns/m/node_modules/@nativescript-community/ui-canvas/canvas.ios"');
expect(rewritten).toContain('from "http://localhost:5173/ns/m/node_modules/@nativescript-community/ui-canvas/index.common"');
});

it('gives a runtime-plugin module the same URL whether it is imported, re-exported or dynamically imported', () => {
const root = mkdtempSync(join(tmpdir(), 'ns-websocket-source-intent-'));
tempRoots.push(root);

const pkgDir = join(root, 'node_modules', '@triniwiz', 'nativescript-masonkit');
mkdirSync(pkgDir, { recursive: true });
writeFileSync(join(root, 'package.json'), JSON.stringify({ name: 'fixture-app' }, null, 2));
writeFileSync(join(pkgDir, 'package.json'), JSON.stringify({ name: '@triniwiz/nativescript-masonkit', main: 'index', nativescript: { platforms: { ios: '8.0.0' } } }, null, 2));
writeFileSync(join(pkgDir, 'framework-registry.js'), 'export const frameworkRegistry = new Map();\n');
writeFileSync(join(pkgDir, 'common.js'), [`import { frameworkRegistry } from './framework-registry';`, `export const registry = frameworkRegistry;`].join('\n'));
writeFileSync(join(pkgDir, 'index.ios.js'), [`export { frameworkRegistry } from './framework-registry';`, `export * from './common';`, `export const load = () => import('./framework-registry');`].join('\n'));

process.chdir(root);

const served = (sourceId: string, viteTransformed: string) => rewriteImports(processCodeForDevice(viteTransformed, false, true, true, sourceId), sourceId, new Map(), new Map(), root, false, undefined, 'http://localhost:5173', true);
const registryUrl = 'http://localhost:5173/ns/m/node_modules/@triniwiz/nativescript-masonkit/framework-registry';

const common = served('/node_modules/@triniwiz/nativescript-masonkit/common.js', [`import { frameworkRegistry } from "/node_modules/@triniwiz/nativescript-masonkit/framework-registry.js";`, `export const registry = frameworkRegistry;`].join('\n'));
expect(common).toContain(`from "${registryUrl}"`);

const index = served('/node_modules/@triniwiz/nativescript-masonkit/index.ios.js', [`export { frameworkRegistry } from "/node_modules/@triniwiz/nativescript-masonkit/framework-registry.js";`, `export * from "/node_modules/@triniwiz/nativescript-masonkit/common.js";`, `export const load = () => import("/node_modules/@triniwiz/nativescript-masonkit/framework-registry.js");`].join('\n'));
expect(index).toContain(`export { frameworkRegistry } from "${registryUrl}"`);
expect(index).toContain(`export * from "http://localhost:5173/ns/m/node_modules/@triniwiz/nativescript-masonkit/common"`);
expect(index).toContain(`import("${registryUrl}")`);
expect(index).not.toContain('framework-registry.js');
});
});
Loading