148 lines
6.9 KiB
JavaScript
148 lines
6.9 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.serverDataLoadersPlugin = serverDataLoadersPlugin;
|
|
const common_1 = require("./common");
|
|
const debug = require('debug')('expo:babel:server-data-loaders');
|
|
const LOADER_EXPORT_NAME = 'loader';
|
|
function serverDataLoadersPlugin(api) {
|
|
const { types: t } = api;
|
|
const routerAbsoluteRoot = api.caller(common_1.getExpoRouterAbsoluteAppRoot);
|
|
const isLoaderBundle = api.caller(common_1.getIsLoaderBundle);
|
|
return {
|
|
name: 'expo-server-data-loaders',
|
|
visitor: {
|
|
ExportDefaultDeclaration(path, state) {
|
|
// Early exit if file is not within the `app/` directory
|
|
if (!isInAppDirectory(state.file.opts.filename ?? '', routerAbsoluteRoot)) {
|
|
return;
|
|
}
|
|
// Only remove default exports in loader-only bundles
|
|
if (!isLoaderBundle) {
|
|
return;
|
|
}
|
|
debug('Loader bundle: removing default export from', state.file.opts.filename);
|
|
markForConstantFolding(state);
|
|
path.remove();
|
|
},
|
|
ExportNamedDeclaration(path, state) {
|
|
// Early exit if file is not within the `app/` directory
|
|
if (!isInAppDirectory(state.file.opts.filename ?? '', routerAbsoluteRoot)) {
|
|
debug('Skipping file outside app directory:', state.file.opts.filename);
|
|
return;
|
|
}
|
|
debug(`Processing ${isLoaderBundle ? 'loader' : 'client'} bundle:`, state.file.opts.filename);
|
|
const { declaration, specifiers } = path.node;
|
|
// Is this a type export like `export type Foo`?
|
|
const isTypeExport = path.node.exportKind === 'type';
|
|
// NOTE(@hassankhan): We should add proper handling for specifiers too
|
|
const hasSpecifiers = specifiers.length > 0;
|
|
if (isTypeExport || hasSpecifiers) {
|
|
return;
|
|
}
|
|
// Handles `export function loader() { ... }`
|
|
if (t.isFunctionDeclaration(declaration)) {
|
|
const name = declaration.id?.name;
|
|
if (name && isLoaderIdentifier(name)) {
|
|
// Mark the file as having a loader (for all bundle types)
|
|
markWithLoaderReference(state);
|
|
if (!isLoaderBundle) {
|
|
// Client bundles: remove loader
|
|
debug('Found and removed loader function declaration');
|
|
markForConstantFolding(state);
|
|
path.remove();
|
|
}
|
|
// Loader bundle: keep the loader
|
|
}
|
|
else if (name && isLoaderBundle) {
|
|
// Loader bundle: remove non-loader function declarations
|
|
debug('Loader bundle: removing non-loader function declaration:', name);
|
|
markForConstantFolding(state);
|
|
path.remove();
|
|
}
|
|
}
|
|
// Handles `export const loader = ...`
|
|
if (t.isVariableDeclaration(declaration)) {
|
|
let hasModified = false;
|
|
// Check if any declaration is a loader
|
|
const hasLoaderDeclaration = declaration.declarations.some((declarator) => {
|
|
const name = t.isIdentifier(declarator.id) ? declarator.id.name : null;
|
|
return name && isLoaderIdentifier(name);
|
|
});
|
|
// Mark the file as having a loader (for all bundle types)
|
|
if (hasLoaderDeclaration) {
|
|
markWithLoaderReference(state);
|
|
}
|
|
if (isLoaderBundle) {
|
|
// Loader bundle: keep only loader declarations, remove others
|
|
declaration.declarations = declaration.declarations.filter((declarator) => {
|
|
const name = t.isIdentifier(declarator.id) ? declarator.id.name : null;
|
|
if (name && !isLoaderIdentifier(name)) {
|
|
debug('Loader bundle: removing non-loader variable declaration:', name);
|
|
hasModified = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
else {
|
|
// Client bundles: remove loader declarations
|
|
declaration.declarations = declaration.declarations.filter((declarator) => {
|
|
const name = t.isIdentifier(declarator.id) ? declarator.id.name : null;
|
|
if (name && isLoaderIdentifier(name)) {
|
|
debug('Found and removed loader variable declaration');
|
|
hasModified = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
if (hasModified) {
|
|
markForConstantFolding(state);
|
|
// If all declarations were removed, remove the export
|
|
if (declaration.declarations.length === 0) {
|
|
path.remove();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
};
|
|
}
|
|
/**
|
|
* Checks if identifier name is `loader`
|
|
*/
|
|
function isLoaderIdentifier(name) {
|
|
return name === LOADER_EXPORT_NAME;
|
|
}
|
|
function assertExpoMetadata(metadata) {
|
|
if (metadata && typeof metadata === 'object') {
|
|
return;
|
|
}
|
|
throw new Error('Expected Babel state.file.metadata to be an object');
|
|
}
|
|
/**
|
|
* Check if file is within the `app/` directory
|
|
*/
|
|
function isInAppDirectory(filePath, routerRoot) {
|
|
const normalizedFilePath = (0, common_1.toPosixPath)(filePath);
|
|
const normalizedAppRoot = (0, common_1.toPosixPath)(routerRoot);
|
|
return normalizedFilePath.startsWith(normalizedAppRoot + '/');
|
|
}
|
|
/**
|
|
* Marks a file for Metro's constant folding. This will work for both development and production bundles.
|
|
*
|
|
* @see packages/@expo/metro-config/src/transform-worker/metro-transform-worker.ts#transformJS
|
|
*/
|
|
function markForConstantFolding(state) {
|
|
assertExpoMetadata(state.file.metadata);
|
|
state.file.metadata.performConstantFolding = true;
|
|
}
|
|
/**
|
|
* Sets the `loaderReference` metadata to the file path. This is used to collect all modules with
|
|
* loaders in the Metro serializer.
|
|
*/
|
|
function markWithLoaderReference(state) {
|
|
assertExpoMetadata(state.file.metadata);
|
|
state.file.metadata.loaderReference = state.file.opts.filename ?? undefined;
|
|
}
|