Skip to content

Commit

Permalink
Merge pull request #94 from ysaskia/fix/indexer-with-svelte-config
Browse files Browse the repository at this point in the history
fix: preprocess svelte file during indexing
  • Loading branch information
JReinhold authored Mar 21, 2023
2 parents ad8c44f + f2eaa13 commit ed9648d
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 1 deletion.
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

0 comments on commit ed9648d

Please sign in to comment.