vite-test-utils-edge
Version:
Test utils for Vite application
392 lines (386 loc) • 13.3 kB
JavaScript
;
const node_fs = require('node:fs');
const node_path = require('node:path');
const createDebug = require('debug');
const pc = require('picocolors');
const vite = require('vite');
const defu = require('defu');
const node_module = require('node:module');
const node_os = require('node:os');
const node_url = require('node:url');
const shared = require('@intlify/shared');
const esbuild = require('esbuild');
let currentContext;
function createTestContext(options = {}) {
const cwd = process.cwd();
const browser = !!options.browser;
const _options = defu.defu(options, {
rootDir: cwd,
mode: "dev",
server: browser ? true : !!options.server,
browser,
browserOptions: {
type: "chromium"
}
});
return setTestContext({ options: _options });
}
function setTestContext(ctx) {
currentContext = ctx;
return currentContext;
}
function useTestContext() {
if (!currentContext) {
throw new Error("No context is available. (Forgot calling setup?)");
}
return currentContext;
}
const DEBUG$1 = createDebug("vite-test-utils:utils");
async function isExists(path) {
try {
await node_fs.promises.access(path, node_fs.constants.F_OK);
return true;
} catch {
return false;
}
}
async function mkTmpDir(prefix) {
const p = node_path.join(node_os.tmpdir(), prefix ? `vite-test-utils-${prefix}-` : "vite-test-utils-");
const dir = await node_fs.promises.mkdtemp(p);
DEBUG$1("mkTmpDir", dir);
return dir;
}
async function dynamicImport(module) {
try {
return import(String(module));
} catch {
throw new Error(`
The dependency '${module}' not found.
Please run 'npm install --save-dev ${module}' or 'yarn add --dev ${module}' or 'pnpm add --save-dev ${module}'
`);
}
}
const _require = node_module.createRequire((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('shared/vite-test-utils-edge.21a779c8.cjs', document.baseURI).href)));
async function dynamicImportCompatibility(fileUrl, isESM) {
return isESM ? import(fileUrl) : new Function("file", "return import(file)")(fileUrl);
}
async function loadConfig(configEnv, {
configFile = "vite.config.ts",
configRoot = process.cwd(),
configFilePath = void 0
} = {}) {
DEBUG$1("loadConfig: configFile ->", configFile);
DEBUG$1("loadConfig: configRoot ->", configRoot);
DEBUG$1("loadConfig: configFilePath ->", configFilePath);
let resolvedPath = configFilePath;
if (resolvedPath == null) {
resolvedPath = node_path.resolve(configRoot, configFile);
}
let isESM = false;
if (/\.m[jt]s$/.test(resolvedPath)) {
isESM = true;
} else if (/\.c[jt]s$/.test(resolvedPath)) {
isESM = false;
} else {
try {
const pkg = await lookupFile(configRoot, ["package.json"]);
isESM = !!pkg && JSON.parse(pkg).type === "module";
} catch (e) {
}
}
try {
const bundled = await bundleConfigFile(resolvedPath, isESM);
const userConfig = await loadConfigFromBundledFile(resolvedPath, bundled.code, isESM);
DEBUG$1(`bundled config file loaded`);
const config = await (typeof userConfig === "function" ? userConfig(configEnv) : userConfig);
if (!shared.isObject(config)) {
throw new Error(`config must export or return an object.`);
}
return {
path: vite.normalizePath(resolvedPath),
config,
dependencies: bundled.dependencies
};
} catch (e) {
console.error(`failed to load config from ${resolvedPath}: ${e.message}`);
throw e;
}
}
async function lookupFile(dir, formats, options) {
for (const format of formats) {
const fullPath = node_path.join(dir, format);
if (await isExists(fullPath) && (await node_fs.promises.stat(fullPath)).isFile()) {
return options?.pathOnly ? fullPath : await node_fs.promises.readFile(fullPath, "utf-8");
}
}
const parentDir = node_path.dirname(dir);
if (parentDir !== dir && (!options?.rootDir || parentDir.startsWith(options?.rootDir))) {
return lookupFile(parentDir, formats, options);
}
}
async function bundleConfigFile(fileName, isESM) {
const dirnameVarName = "__vite_injected_original_dirname";
const filenameVarName = "__vite_injected_original_filename";
const importMetaUrlVarName = "__vite_injected_original_import_meta_url";
const result = await esbuild.build({
absWorkingDir: process.cwd(),
entryPoints: [fileName],
outfile: "out.js",
write: false,
target: ["node14.18", "node16"],
platform: "node",
bundle: true,
format: isESM ? "esm" : "cjs",
sourcemap: "inline",
metafile: true,
define: {
__dirname: dirnameVarName,
__filename: filenameVarName,
"import.meta.url": importMetaUrlVarName
},
plugins: [
{
name: "externalize-deps",
setup(build2) {
build2.onResolve({ filter: /.*/ }, async ({ path: id, importer }) => {
if (id[0] !== "." && !node_path.isAbsolute(id)) {
return {
external: true
};
}
const idFsPath = node_path.resolve(node_path.dirname(importer), id);
const idPkgPath = await lookupFile(idFsPath, [`package.json`], {
pathOnly: true
});
if (idPkgPath) {
const idPkgDir = node_path.dirname(idPkgPath);
if (node_path.relative(idPkgDir, fileName).startsWith("..")) {
return {
path: node_url.pathToFileURL(idFsPath).href,
external: true
};
}
}
});
}
},
{
name: "inject-file-scope-variables",
setup(build2) {
build2.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => {
const contents = await node_fs.promises.readFile(args.path, "utf8");
const injectValues = `const ${dirnameVarName} = ${JSON.stringify(node_path.dirname(args.path))};const ${filenameVarName} = ${JSON.stringify(args.path)};const ${importMetaUrlVarName} = ${JSON.stringify(node_url.pathToFileURL(args.path).href)};`;
return {
loader: args.path.endsWith("ts") ? "ts" : "js",
contents: injectValues + contents
};
});
}
}
]
});
const { text } = result.outputFiles[0];
return {
code: text,
dependencies: result.metafile ? Object.keys(result.metafile.inputs) : []
};
}
async function loadConfigFromBundledFile(fileName, bundledCode, isESM) {
DEBUG$1("loadConfigFromBundledFile: ", fileName, isESM);
if (isESM) {
const fileBase = `${fileName}.timestamp-${Date.now()}`;
const fileNameTmp = `${fileBase}.mjs`;
const fileUrl = `${node_url.pathToFileURL(fileBase)}.mjs`;
await node_fs.promises.writeFile(fileNameTmp, bundledCode);
try {
return await dynamicImportCompatibility(fileUrl, isESM).then((mod) => mod.default || mod);
} finally {
await node_fs.promises.unlink(fileNameTmp);
}
} else {
const extension = node_path.extname(fileName);
const realFileName = await node_fs.promises.realpath(fileName);
const loaderExt = extension in _require.extensions ? extension : ".js";
const defaultLoader = _require.extensions[loaderExt];
_require.extensions[loaderExt] = (module, filename) => {
if (filename === realFileName) {
module._compile(bundledCode, filename);
} else {
defaultLoader(module, filename);
}
};
delete _require.cache[_require.resolve(fileName)];
const raw = _require(fileName);
_require.extensions[loaderExt] = defaultLoader;
return raw.__esModule ? raw.default : raw;
}
}
function stringifyObj(obj) {
return `Object({${Object.entries(obj).map(([key, value]) => `${JSON.stringify(key)}:${toCode(value)}`).join(`,`)}})`;
}
function toCode(code) {
if (code === null) {
return `null`;
}
if (code === void 0) {
return `undefined`;
}
if (shared.isString(code)) {
return JSON.stringify(code);
}
if (shared.isRegExp(code) && code.toString) {
return code.toString();
}
if (shared.isFunction(code) && code.toString) {
return `(${code.toString()})`;
}
if (shared.isArray(code)) {
return `[${code.map((c) => toCode(c)).join(`,`)}]`;
}
if (shared.isObject(code)) {
return stringifyObj(code);
}
return code + ``;
}
const DEBUG = createDebug("vite-test-utils:vite");
function getFixtureContextFrom(env) {
const options = {};
options.port = env.__VTU_PORT != null ? parseInt(env.__VTU_PORT) : 3e3;
options.mode = env.__VTU_MODE != null && ["dev", "preview"].includes(env.__VTU_MODE) ? env.__VTU_MODE : "dev";
options.root = env.__VTU_FIXTURE_ROOT ?? process.cwd();
options.buildDir = env.__VTU_FIXTURE_BUILD_DIR;
options.configFile = env.__VTU_FIXTURE_CONFIG_FILE;
options.viteConfig = env.__VTU_FIXTURE_VITE_CONFIG;
options.viteConfigFile = env.__VTU_FIXTURE_VITE_CONFIG_FILE;
return options;
}
const DEFAULT_CONFIG_FILES = [
"vite.config.mjs",
"vite.config.mts",
"vite.config.cjs",
"vite.config.cts",
"vite.config.ts",
"vite.config.js"
];
async function resolveViteConfig(dir, {
config = "vite.config.ts",
viteDefaultConfigCheck = true
} = {}) {
DEBUG("resolveViteConfig: ", dir, config, viteDefaultConfigCheck);
if (!await isExists(dir)) {
DEBUG("resolveViteConfig: dir not exists", dir);
return [false, null];
}
const configFiles = viteDefaultConfigCheck ? DEFAULT_CONFIG_FILES : [config];
DEBUG("resolveViteConfig: configFiles -> ", configFiles);
let found = false;
let resolveViteConfig2 = null;
for (const config2 of configFiles) {
const target = node_path.resolve(dir, config2);
DEBUG("resolveViteConfig: target -> ", target);
if (await isExists(target)) {
found = true;
resolveViteConfig2 = target;
break;
}
}
DEBUG("resolveViteConfig: final -> ", found, resolveViteConfig2);
return [found, resolveViteConfig2];
}
async function resolveFixture(fixtureDir, config) {
if (config) {
const [found2] = await resolveViteConfig(fixtureDir, { config, viteDefaultConfigCheck: false });
if (found2) {
return true;
}
}
let found = false;
const dirs = [fixtureDir, process.cwd()];
for (const dir of dirs) {
const [_found, resolvedViteConfig] = await resolveViteConfig(dir);
DEBUG("resolveFixture:", dir, found, resolvedViteConfig);
if (_found) {
found = true;
break;
}
}
DEBUG("resolvedViteConfig: final", found);
return found;
}
function getViteCommand(mode) {
return mode === "dev" ? "serve" : "build";
}
function getViteMode(mode) {
return mode === "dev" ? "development" : "production";
}
async function loadViteConfig(configRoot, configFile, optionName, mode) {
DEBUG("loadViteConfig:", configRoot, configFile, optionName, mode);
const result = await loadConfig(
{
command: getViteCommand(mode),
mode: getViteMode(mode)
},
{
configFilePath: configFile,
configRoot
}
);
if (result == null) {
console.warn(pc.yellow(pc.bold(`Cannot load from '${optionName}' option`)));
return {};
} else {
return result.config;
}
}
async function writeViteConfigOptions(options) {
const tmp = await mkTmpDir("vite-config-inline");
const configTmp = node_path.resolve(tmp, "vite.config.mjs");
DEBUG("writeViteConfigOptions: configTmp -> ", configTmp);
await node_fs.promises.writeFile(configTmp, `export default ${toCode(options)}`, "utf-8");
return configTmp;
}
async function mkBuildDir() {
return await mkTmpDir(Math.random().toString(36).slice(2, 8));
}
async function prepareFixture() {
const ctx = useTestContext();
if (ctx.options.viteConfig) {
ctx.viteConfigInline = await writeViteConfigOptions(ctx.options.viteConfig);
}
if (ctx.options.mode === "preview") {
ctx.buildDir = await mkBuildDir();
}
}
async function loadFixture(env) {
const ctx = getFixtureContextFrom(env || process.env);
const { mode, root, configFile, viteConfig, viteConfigFile } = ctx;
if (!await resolveFixture(root, configFile)) {
console.warn(pc.yellow(pc.bold(`vite config has been not found in ${root}`)));
console.warn(
pc.yellow(
pc.bold(
"The fixture that will work from now on will follow the vite defaults or you have specified options that are `viteConfig` or `viteConfigFile` options."
)
)
);
}
ctx.vite = viteConfigFile ? await loadViteConfig(root, node_path.resolve(root, viteConfigFile), "viteConfigFile", mode) : viteConfig ? await loadViteConfig(root, viteConfig, "viteConfig", mode) : {};
ctx.vite = vite.mergeConfig(ctx.vite, {
root,
logLevel: mode === "preview" ? "silent" : "info",
build: {
outDir: mode === "preview" ? ctx.buildDir : void 0
}
});
return ctx;
}
async function buildFixture(ctx) {
await vite.build(ctx.vite);
}
exports.buildFixture = buildFixture;
exports.createTestContext = createTestContext;
exports.dynamicImport = dynamicImport;
exports.loadFixture = loadFixture;
exports.prepareFixture = prepareFixture;
exports.setTestContext = setTestContext;
exports.useTestContext = useTestContext;