first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.expoUseDomDirectivePlugin = expoUseDomDirectivePlugin;
const node_crypto_1 = __importDefault(require("node:crypto"));
const node_path_1 = require("node:path");
const node_url_1 = __importDefault(require("node:url"));
const common_1 = require("./common");
function expoUseDomDirectivePlugin(api) {
const { template, types: t } = api;
const isProduction = api.caller(common_1.getIsProd);
const platform = api.caller((caller) => caller?.platform);
const projectRoot = api.caller(common_1.getPossibleProjectRoot);
return {
name: 'expo-use-dom-directive',
visitor: {
Program(path, state) {
// Native only feature.
if (platform === 'web') {
return;
}
const hasUseDomDirective = path.node.directives.some((directive) => directive.value.value === 'use dom');
const filePath = state.file.opts.filename;
if (!filePath) {
// This can happen in tests or systems that use Babel standalone.
throw new Error('[Babel] Expected a filename to be set in the state');
}
// File starts with "use dom" directive.
if (!hasUseDomDirective) {
// Do nothing for code that isn't marked as a dom component.
return;
}
let displayName = 'Component';
// Assert that a default export must exist and that no other exports should be present.
// NOTE: In the future we could support other exports with extraction.
let hasDefaultExport = false;
// Collect all of the exports
path.traverse({
ExportNamedDeclaration(path) {
const declaration = path.node.declaration;
if (t.isTypeAlias(declaration) ||
t.isInterfaceDeclaration(declaration) ||
t.isTSTypeAliasDeclaration(declaration) ||
t.isTSInterfaceDeclaration(declaration)) {
// Allows type exports
return;
}
throw path.buildCodeFrameError('Modules with the "use dom" directive only support a single default export.');
},
ExportDefaultDeclaration(path) {
hasDefaultExport = true;
if (t.isFunctionDeclaration(path.node.declaration) && path.node.declaration.id) {
displayName = path.node.declaration.id.name;
}
},
});
if (!hasDefaultExport) {
throw path.buildCodeFrameError('The "use dom" directive requires a default export to be present in the file.');
}
// Assert that _layout routes cannot be used in DOM components.
const fileBasename = (0, node_path_1.basename)(filePath);
if (projectRoot &&
// Detecting if the file is in the router root would be extensive as it would cause a more complex
// cache key for each file. Instead, let's just check if the file is in the project root and is not a node_module,
// then we can assert that users should not use `_layout` or `+api` with "use dom".
filePath.includes(projectRoot) &&
!filePath.match(/node_modules/)) {
if (fileBasename.match(/^_layout\.[jt]sx?$/)) {
throw path.buildCodeFrameError('Layout routes cannot be marked as DOM components because they cannot render native views.');
}
else if (
// No API routes
fileBasename.match(/\+api\.[jt]sx?$/)) {
throw path.buildCodeFrameError('API routes cannot be marked as DOM components.');
}
}
const outputKey = node_url_1.default.pathToFileURL(filePath).href;
// Removes all imports using babel API, that will disconnect import bindings from the program.
// plugin-transform-typescript TSX uses the bindings to remove type imports.
// If the DOM component has `import React from 'react';`,
// the plugin-transform-typescript treats it as an typed import and removes it.
// That will further cause undefined `React` error.
path.traverse({
ImportDeclaration(path) {
path.remove();
},
});
path.node.body = [];
path.node.directives = [];
// Create template with declaration first
const proxyModuleTemplate = `
import React from 'react';
import { WebView } from 'expo/dom/internal';
${isProduction
? `const filePath = "${node_crypto_1.default.createHash('md5').update(outputKey).digest('hex')}.html";`
: `const filePath = "${fileBasename}?file=" + ${JSON.stringify(outputKey)};`}
const _Expo_DOMProxyComponent = React.forwardRef((props, ref) => {
return React.createElement(WebView, { ref, ...props, filePath });
});
if (__DEV__) _Expo_DOMProxyComponent.displayName = ${JSON.stringify(`DOM(${displayName})`)};
export default _Expo_DOMProxyComponent;
`;
// Convert template to AST and push to body
const ast = template.ast(proxyModuleTemplate);
const results = path.pushContainer('body', ast);
// Find and register the component declaration
results.forEach((nodePath) => {
if (t.isVariableDeclaration(nodePath.node) &&
'name' in nodePath.node.declarations[0]?.id &&
nodePath.node.declarations[0].id.name === '_Expo_DOMProxyComponent') {
path.scope.registerDeclaration(nodePath);
}
});
assertExpoMetadata(state.file.metadata);
// Save the client reference in the metadata.
state.file.metadata.expoDomComponentReference = outputKey;
},
},
};
}
function assertExpoMetadata(metadata) {
if (metadata && typeof metadata === 'object') {
return;
}
throw new Error('Expected Babel state.file.metadata to be an object');
}