Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: preprocess svelte file during indexing #94

Merged
merged 10 commits into from
Mar 21, 2023
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
},
"dependencies": {
"@babel/runtime": "^7.17.5",
"fs-extra": "^11.1.1",
"magic-string": "^0.26.6",
"ts-dedent": "^2.0.0"
},
Expand Down
188 changes: 188 additions & 0 deletions src/config-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// This file is a rewrite of `@sveltejs/vite-plugin-svelte` without the `Vite`
// parts: https://github.com/sveltejs/vite-plugin-svelte/blob/e8e52deef93948da735c4ab69c54aced914926cf/packages/vite-plugin-svelte/src/utils/load-svelte-config.ts
import { logger } from '@storybook/client-logger';
import { createRequire } from 'module';
import path from 'path';
import fs from 'fs';
import { pathExists } from "fs-extra";
import { pathToFileURL, fileURLToPath } from 'url';

/**
* Try find svelte config and then load it.
*
* @returns {import('@sveltejs/kit').Config | undefined}
* Returns the svelte configuration object.
*/
export async function loadSvelteConfig() {
const configFile = await findSvelteConfig();

// no need to throw error since we handle projects without config files
if (configFile === undefined) {
return undefined;
}

let err;

// try to use dynamic import for svelte.config.js first
if (configFile.endsWith('.js') || configFile.endsWith('.mjs')) {
try {
return importSvelteOptions(configFile);
} catch (e) {
logger.error(`failed to import config ${configFile}`, e);
err = e;
}
}
// cjs or error with dynamic import
if (configFile.endsWith('.js') || configFile.endsWith('.cjs')) {
try {
return requireSvelteOptions(configFile);
} catch (e) {
logger.error(`failed to require config ${configFile}`, e);
if (!err) {
err = e;
}
}
}
// failed to load existing config file
throw new Error(`failed to load config ${configFile}`, { cause: err });
}

const importSvelteOptions = (() => {
// hide dynamic import from ts transform to prevent it turning into a require
// see https://github.com/microsoft/TypeScript/issues/43329#issuecomment-811606238
// also use timestamp query to avoid caching on reload
const dynamicImportDefault = new Function(
'path',
'timestamp',
'return import(path + "?t=" + timestamp).then(m => m.default)'
);

/**
* Try import specified svelte configuration.
*
* @param {string} configFile
* Absolute path of the svelte config file to import.
*
* @returns {import('@sveltejs/kit').Config}
* Returns the svelte configuration object.
*/
return async (configFile) => {
const result = await dynamicImportDefault(
pathToFileURL(configFile).href,
fs.statSync(configFile).mtimeMs
);

if (result != null) {
return { ...result, configFile };
}
throw new Error(`invalid export in ${configFile}`);
};
})();

const requireSvelteOptions = (() => {
/** @type {NodeRequire} */
let esmRequire;

/**
* Try import specified svelte configuration.
*
* @param {string} configFile
* Absolute path of the svelte config file to require.
*
* @returns {import('@sveltejs/kit').Config}
* Returns the svelte configuration object.
*/
return (configFile) => {
// identify which require function to use (esm and cjs mode)
const requireFn = import.meta.url
? (esmRequire = esmRequire ?? createRequire(import.meta.url))
: require;

// avoid loading cached version on reload
delete requireFn.cache[requireFn.resolve(configFile)];
const result = requireFn(configFile);

if (result != null) {
return { ...result, configFile };
}
throw new Error(`invalid export in ${configFile}`);
};
})();

/**
* Try find svelte config. First in current working dir otherwise try to
* find it by climbing up the directory tree.
*
* @returns {Promise<string | undefined>}
* Returns the absolute path of the config file.
*/
async function findSvelteConfig() {
const lookupDir = process.cwd();
let configFiles = await getConfigFiles(lookupDir);

if (configFiles.length === 0) {
configFiles = await getConfigFilesUp();
}
if (configFiles.length === 0) {
return undefined;
}
if (configFiles.length > 1) {
logger.warn(
'Multiple svelte configuration files were found, which is unexpected. The first one will be used.',
configFiles
);
}
return configFiles[0];
}

/**
* Gets the file path of the svelte config by walking up the tree.
* Returning the first found. Should solves most of monorepos with
* only one config at workspace root.
*
* @returns {Promise<string[]>}
* Returns an array containing all available config files.
*/
async function getConfigFilesUp() {
const importPath = fileURLToPath(import.meta.url);
const pathChunks = path.dirname(importPath).split(path.sep);

while (pathChunks.length) {
pathChunks.pop();

const parentDir = pathChunks.join(path.posix.sep);
// eslint-disable-next-line no-await-in-loop
const configFiles = await getConfigFiles(parentDir);

if (configFiles.length !== 0) {
return configFiles;
}
}
return [];
}

/**
* Gets all svelte config from a specified `lookupDir`.
*
* @param {string} lookupDir
* Directory in which to look for svelte files.
*
* @returns {Promise<string[]>}
* Returns an array containing all available config files.
*/
async function getConfigFiles(lookupDir) {
/** @type {[string, boolean][]} */
const fileChecks = await Promise.all(
knownConfigFiles.map(async (candidate) => {
const filePath = path.resolve(lookupDir, candidate);
return [filePath, await pathExists(filePath)];
})
);

return fileChecks.reduce((files, [file, exists]) => {
if (exists) files.push(file);
return files;
}, []);
}

const knownConfigFiles = ['js', 'cjs', 'mjs'].map((ext) => `svelte.config.${ext}`);
9 changes: 8 additions & 1 deletion src/preset/indexer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { extractStories } from '../parser/extract-stories';
import fs from 'fs-extra';
import * as svelte from 'svelte/compiler';
import { extractStories } from '../parser/extract-stories';
import { loadSvelteConfig } from '../config-loader';

export async function svelteIndexer(fileName, { makeTitle }) {
let code = (await fs.readFile(fileName, 'utf-8')).toString();
const svelteOptions = await loadSvelteConfig();

if (svelteOptions && svelteOptions.preprocess) {
code = (await svelte.preprocess(code, svelteOptions.preprocess, { filename: fileName })).code;
}

const defs = extractStories(code);

Expand Down
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7784,6 +7784,15 @@ fs-extra@^10.1.0:
jsonfile "^6.0.1"
universalify "^2.0.0"

fs-extra@^11.1.1:
version "11.1.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"

fs-extra@^9.0.1:
version "9.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
Expand Down