UNPKG

@rechunk/babel-plugin

Version:

Babel plugin for transforming and processing ReChunk directives in React Native applications

302 lines (276 loc) 12.5 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { default: () => index_default, getIsRechunkBundler: () => getIsRechunkBundler, withBabelPresetExpoOptions: () => withBabelPresetExpoOptions, withReactNativeBabelPresetOptions: () => withReactNativeBabelPresetOptions }); module.exports = __toCommonJS(index_exports); var import_core = require("@babel/core"); var t = __toESM(require("@babel/types")); var import_utils = require("@rechunk/utils"); var import_chalk = __toESM(require("chalk")); var import_dedent = __toESM(require("dedent")); var import_path = require("path"); function getIsRechunkBundler(caller) { return caller.name === "rechunk"; } var NODE_PROCESS_IDENTIFIER = "process"; var NODE_PROCESS_ENV_IDENTIFIER = "env"; var RECHUNK_CONFIG_KEY = "@rechunk+config+json"; var RECHUNK_PACKAGE_KEY = "@rechunk+package+json"; var RECHUNK_DEV_SERVER_HOST = "http://localhost:49904"; function index_default(api) { const isRechunkBundler = api.caller(getIsRechunkBundler); const isOffline = process.env.RECHUNK_ENVIRONMENT === "offline"; const fileCache = /* @__PURE__ */ new Map(); function getCachedJson(key, jsonFilename) { let jsonData = fileCache.get(key); if (!jsonData) { jsonData = (0, import_utils.findClosestJSON)(jsonFilename); fileCache.set(key, jsonData); } return jsonData; } return { name: "rechunk-babel-plugin", visitor: { Program(path, state) { const hasUseRechunkDirective = path.node.directives.some( (directive) => directive.value.value === "use rechunk" ); const filePath = state.file.opts.filename; if (!filePath) { throw new Error("[Babel] Expected a filename to be set in the state"); } if (!hasUseRechunkDirective || isOffline || isRechunkBundler) { return; } let hasDefaultExport = false; let hasAsyncModifier = false; let isFunction = state.filename.match(/\.ts$/); path.traverse({ ExportNamedDeclaration(path2) { const declaration = path2.node.declaration; if (t.isTypeAlias(declaration) || t.isInterfaceDeclaration(declaration) || t.isTSTypeAliasDeclaration(declaration) || t.isTSInterfaceDeclaration(declaration)) { return; } throw path2.buildCodeFrameError( import_chalk.default.red( import_dedent.default`\n\nError: Named exports found. The "use rechunk" directive requires that the module contain only a single default export. Having named exports is not supported and will lead to this error. Steps to resolve this issue: 1. Open the file where the error occurred. 2. Verify that only one default export is present in the module. Example of a valid single default export: export default function MyComponent() { // Your code here } 3. If there are named exports, consolidate them into a single default or refactor your code. For more details on the "use rechunk" directive and export requirements, refer to the ReChunk documentation. ` ) ); }, ExportDefaultDeclaration(path2) { const declaration = path2.node.declaration; if (isFunction && t.isFunctionDeclaration(declaration)) { hasAsyncModifier = declaration.async; } hasDefaultExport = true; } }); if (!hasDefaultExport) { throw path.buildCodeFrameError( import_chalk.default.red( import_dedent.default`\n\nError: Missing default export. The "use rechunk" directive requires the module to have a default export. Without a default export, the chunk cannot be processed correctly. Steps to resolve this issue: 1. Open the file where the error occurred. 2. Add a default export to the module. Example of a valid default export: export default function MyComponent() { // Your code here } 3. Ensure there is only one default export in the file. For more details on the "use rechunk" directive and default export requirements, refer to the ReChunk documentation. ` ) ); } if (isFunction && !hasAsyncModifier) { throw path.buildCodeFrameError( import_chalk.default.red( import_dedent.default`\n\nError: Missing async default export. The "use rechunk" directive in a function module requires the default export to be an async function. Without an async default export, the chunk cannot be processed correctly. Steps to resolve this issue: 1. Open the file where the error occurred. 2. Ensure the default export is an async function. Example of a valid async default export: export default async function loadChunk() { // Your async code here } 3. Ensure there is only one default export in the file and that it includes the 'async' keyword. For more details on the "use rechunk" directive and async default export requirements, refer to the ReChunk documentation. ` ) ); } path.traverse({ ImportDeclaration(path2) { path2.remove(); } }); path.node.body = []; path.node.directives = []; let relativePath = (0, import_path.relative)(process.cwd(), state.filename); if (!relativePath.startsWith(".")) { relativePath = `./${relativePath}`; } const bufferObj = Buffer.from(relativePath, "utf8"); const base64String = bufferObj.toString("base64"); const rechunkComponentTemplate = ` import React from 'react'; import {importChunk} from '@rechunk/core'; const $$ReChunkModule = React.lazy(() => importChunk('${base64String}')); export default $$ReChunkModule; `; const rechunkFunctionTemplate = ` import { importChunk } from '@rechunk/core'; const chunkLoader = importChunk('${base64String}'); async function $$ReChunkModule(...args) { return chunkLoader.then(module => { return module.default(...args); }); } export default $$ReChunkModule; `; const ast = import_core.template.ast( isFunction ? rechunkFunctionTemplate : rechunkComponentTemplate ); const results = path.pushContainer("body", ast); results.forEach((nodePath) => { if (t.isVariableDeclaration(nodePath.node) && nodePath.node.declarations[0]?.id && "name" in nodePath.node.declarations[0].id && nodePath.node.declarations[0].id.name === "$$ReChunkModule") { path.scope.registerDeclaration(nodePath); } }); }, /** * Visits MemberExpression nodes and inserts the rechunk project and readKey * as a configuration object argument into process.env.__RECHUNK_USERNAME__ * and process.env.__RECHUNK_PASSWORD__. * @param {Babel.NodePath<Babel.types.MemberExpression>} path - Babel path object. * @param {Babel.types.MemberExpression} path.node - The current AST node member. * @param {Babel.NodePath<Babel.types.Node>} path.parentPath - The parent path of the current AST node member. */ MemberExpression({ node, buildCodeFrameError, parentPath: parent }) { if (!t.isIdentifier(node.object, { name: NODE_PROCESS_IDENTIFIER }) || !t.isIdentifier(node.property, { name: NODE_PROCESS_ENV_IDENTIFIER })) { return; } if (!t.isMemberExpression(parent.node)) { return; } const rechunkJson = getCachedJson( RECHUNK_CONFIG_KEY, ".rechunkrc.json" ); const packageJson = getCachedJson(RECHUNK_PACKAGE_KEY, "package.json"); const { host, project, readKey, publicKey } = rechunkJson; if (t.isIdentifier(parent.node.property, { name: "__RECHUNK_PROJECT__" }) && !parent.parentPath?.isAssignmentExpression()) { parent.replaceWith(t.stringLiteral(project)); } if (t.isIdentifier(parent.node.property, { name: "__RECHUNK_READ_KEY__" }) && !parent.parentPath?.isAssignmentExpression()) { parent.replaceWith(t.stringLiteral(readKey)); } if (t.isIdentifier(parent.node.property, { name: "__RECHUNK_HOST__" }) && !parent.parentPath?.isAssignmentExpression()) { if (process.env.RECHUNK_ENVIRONMENT === "prod" || !process.env.RECHUNK_ENVIRONMENT) { parent.replaceWith(t.stringLiteral(host)); } if (process.env.RECHUNK_ENVIRONMENT === "dev") { if (!(0, import_utils.isRechunkDevServerRunning)()) throw buildCodeFrameError( import_chalk.default.red(import_dedent.default`\n\nRECHUNK_ENVIRONMENT is set to "dev", but no development server was detected. To use the "dev" environment, please ensure that the development server is running. You can start it by executing the following command: rechunk dev-server If you intended to use a different environment: - Set RECHUNK_ENVIRONMENT to "prod" to fetch components from a remote server using '.rechunkrc.json'. - Set RECHUNK_ENVIRONMENT to "offline" to load components directly without a server connection. For example: export RECHUNK_ENVIRONMENT="prod" # For remote server export RECHUNK_ENVIRONMENT="offline" # For offline mode`) ); parent.replaceWith(t.stringLiteral(RECHUNK_DEV_SERVER_HOST)); } } if (t.isIdentifier(parent.node.property, { name: "__RECHUNK_PUBLIC_KEY__" }) && parent.parentPath?.isAssignmentExpression()) { parent.replaceWith(t.stringLiteral(publicKey)); } } } }; } function withReactNativeBabelPresetOptions(api, options = {}) { const isRechunk = api.caller(getIsRechunkBundler); return { ...options, enableBabelRuntime: !isRechunk, disableImportExportTransform: isRechunk }; } function withBabelPresetExpoOptions(api, options = {}) { const isRechunk = api.caller(getIsRechunkBundler); return { ...options, enableBabelRuntime: !isRechunk }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { getIsRechunkBundler, withBabelPresetExpoOptions, withReactNativeBabelPresetOptions });